// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLSTENCIL_H__
#include <atlstencil.h>
#endif

#ifndef __ATLISAPI_H__
#define __ATLISAPI_H__

#pragma once
#include <atlbase.h>
#include <time.h>   // needed for cookie support
#include <httpext.h>    // needed for ECB and IIS support
#include <atlspriv.h>
#include <atlserr.h>
#include <atlfile.h>
#include <atlstr.h>
#include <atldbcli.h>
#include <atlutil.h>
#include <atlcache.h>
#include <atlsrvres.h>
#include <atlsiface.h>
#include <objbase.h>
#include <atlsecurity.h>
#include <errno.h>
#ifndef ATL_NO_SOAP
#include <msxml2.h>
#endif
#ifndef ATL_NO_ACLAPI
#include <aclapi.h>
#endif
#ifndef ATL_NO_MMSYS
#pragma warning(push)
#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union
#include <mmsystem.h>
#pragma warning(pop)
#ifndef _ATL_NO_DEFAULT_LIBS
#pragma comment(lib, "winmm.lib")
#ifndef ATL_NO_SOAP
#pragma comment(lib, "msxml2.lib")
#endif
#endif  // !_ATL_NO_DEFAULT_LIBS
#endif
#include <atlpath.h>

#pragma warning(push)
#pragma warning(disable: 4291) // allow placement new
#pragma warning(disable: 4127) // conditional expression is constant
#pragma warning(disable: 4511) // copy constructor could not be generated
#pragma warning(disable: 4512) // assignment operator could not be generated
#pragma warning(disable: 4625) // copy constructor could not be generated because a base class copy constructor is inaccessible
#pragma warning(disable: 4626) // assignment operator could not be generated because a base class assignment operator is inaccessible
#pragma warning(disable: 4191) // unsafe conversion from 'functionptr1' to 'functionptr2'
#pragma warning(disable: 4702) // unreachable code

#include <initguid.h>
#include <dbgautoattach.h>

#ifndef SESSION_COOKIE_NAME
#define SESSION_COOKIE_NAME "SESSIONID"
#endif

// override this if you want to use a different CLSID for SAX
#ifndef ATLS_SAXXMLREADER_CLSID
#define ATLS_SAXXMLREADER_CLSID __uuidof(SAXXMLReader)
#endif // ATLS_SAXXMLREADER_CLSID


// This function is used in CValidateObject to determine if an empty
// request parameter really should be empty. You can
// specialize this function in your own code such as
// the following specialization for type long:
// template <>
// inline bool IsNullByType<long>(long type) throw()
// {
//   return type == 0;
// }
// You should provide your own specialization for this
// function if the comparison of type==0 is not adequate
// to discover whether or not your type is 0.
template <class TComp>
inline bool IsNullByType(__in TComp type) throw()
{
    return type == 0;
}


#pragma pack(push,_ATL_PACKING)
namespace ATL
{

// Default file extension for server response files
#ifndef ATL_DEFAULT_STENCIL_EXTENSION
#define ATL_DEFAULT_STENCIL_EXTENSION ".srf"
#endif
extern __declspec(selectany) const char * const c_AtlSRFExtension = ATL_DEFAULT_STENCIL_EXTENSION;
extern __declspec(selectany) const TCHAR * const c_tAtlSRFExtension = _T(ATL_DEFAULT_STENCIL_EXTENSION);
#define ATLS_EXTENSION_LEN (sizeof(ATL_DEFAULT_STENCIL_EXTENSION)-2)

// Default file extension for handler DLLs
#ifndef ATL_DEFAULT_DLL_EXTENSION
#define ATL_DEFAULT_DLL_EXTENSION ".dll"
#endif
extern __declspec(selectany) const char * const c_AtlDLLExtension = ATL_DEFAULT_DLL_EXTENSION;
extern __declspec(selectany) const TCHAR * const c_tAtlDLLExtension = _T(ATL_DEFAULT_DLL_EXTENSION);
#define ATLS_DLL_EXTENSION_LEN (sizeof(ATL_DEFAULT_DLL_EXTENSION)-2)

// maximum handler name length
#ifndef ATL_MAX_HANDLER_NAME_LEN
#define ATL_MAX_HANDLER_NAME_LEN 64
#endif

#ifndef ATL_HANDLER_NAME_DEFAULT
#define ATL_HANDLER_NAME_DEFAULT "Default"
#endif // ATL_DEFAULT_HANDLER_NAME


// maximum timeout for async guard mutex
#ifndef ATLS_ASYNC_MUTEX_TIMEOUT
#define ATLS_ASYNC_MUTEX_TIMEOUT 10000
#endif

#if defined(_M_IA64) || defined (_M_AMD64)
#define ATLS_FUNCID_INITIALIZEHANDLERS "InitializeAtlHandlers"
#define ATLS_FUNCID_GETATLHANDLERBYNAME "GetAtlHandlerByName"
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "UninitializeAtlHandlers"
#elif defined(_M_IX86)
#define ATLS_FUNCID_INITIALIZEHANDLERS "_InitializeAtlHandlers@8"
#define ATLS_FUNCID_GETATLHANDLERBYNAME "_GetAtlHandlerByName@12"
#define ATLS_FUNCID_UNINITIALIZEHANDLERS "_UninitializeAtlHandlers@0"
#else
#error Unknown Platform.
#endif

#define ATL_MAX_COOKIE_LEN 2048
#define ATL_MAX_COOKIE_ELEM 1024


// Defines a small value used for comparing the equality of floating point numbers.
#ifndef ATL_EPSILON
#define ATL_EPSILON .0001
#endif

#ifndef ATL_DEFAULT_PRECISION
#define ATL_DEFAULT_PRECISION 6
#endif

// Call this function to URL-encode a buffer and have the result appended to a CString passed by reference.
//
// A space in the input string is encoded as a plus sign (+).
// Other unsafe characters (as determined by AtlIsUnsafeUrlChar) are encoded as escaped octets.
// An escaped octet is a percent sign (%) followed by two digits representing the hexadecimal code of the character.
//
// string       A CStringA reference to which will be appended the encoded version of szBuf.
//
// szBuf        The string to be URL-encoded.
ATL_NOINLINE inline bool EscapeToCString(__inout CStringA& string, __in LPCSTR szBuf)
{
    ATLENSURE(szBuf != NULL);

    _ATLTRY
    {
        CHAR szEscaped[512];
        LPSTR pszStr = szEscaped;
        DWORD dwLen = 0;

        while(*szBuf)
        {
            if(dwLen + 4 >= _countof(szEscaped))
            {
                *pszStr = '\0';
                string.Append(szEscaped, dwLen);
                pszStr = szEscaped;
                dwLen = 0;
            }
            if(AtlIsUnsafeUrlChar(*szBuf))
            {
                if(*szBuf == ' ')
                {
                    dwLen++;
                    *pszStr++ = '+';
                }
                else
                {
                    LPSTR pszTmp = pszStr;
                    *pszTmp++ = '%';
                    unsigned char ch = (unsigned char) * szBuf;
                    if(ch < 16)
                    {
                        *pszTmp++ = '0';
                    }
                    Checked::ultoa_s((unsigned char)ch, pszTmp, szEscaped + _countof(szEscaped) - pszTmp, 16);
                    pszStr += sizeof("%FF") - 1;
                    dwLen += sizeof("%FF") - 1;
                }
            }
            else
            {
                *pszStr++ = *szBuf;
                dwLen++;
            }
            szBuf++;
        }

        *pszStr = '\0';
        string.Append(szEscaped, dwLen);
    }
    _ATLCATCHALL()
    {
        return false;
    }

    return true;
}

// UNICODE overload for EscapeToCString
// follow specifications detailed in RFC document on
// Internationalized Uniform Resource Identifiers (IURI)
inline bool EscapeToCString(__inout CStringA& string, __in_z LPCWSTR wszBuf) throw()
{
    _ATLTRY
    {
        // convert string to UTF8
        CFixedStringT<CStringA, 2048> strConvert;

        // get the required length for conversion
        int nSrcLen = (int) wcslen(wszBuf);
        int nLen = AtlUnicodeToUTF8(wszBuf, nSrcLen, NULL, 0);
        if(!nLen)
        {
            return false;
        }

        // allocate MBCS conversion string
        LPSTR sz = strConvert.GetBuffer(nLen + 1);
        if(!sz)
        {
            return false;
        }

        // do the UNICODE to UTF8 conversion
        nLen = AtlUnicodeToUTF8(wszBuf, nSrcLen, sz, nLen);
        if(!nLen)
        {
            return false;
        }

        // null-terminate
        sz[nLen] = '\0';

        // delegate to ANSI version of EscapeToCString
        if(!EscapeToCString(string, sz))
        {
            return false;
        }

        strConvert.ReleaseBuffer(nLen);
    }
    _ATLCATCHALL()
    {
        return false;
    }

    return true;
}

struct CDefaultErrorProvider
{
    struct HTTP_ERROR_TEXT
    {
        UINT uHttpError;    // the Http Error value
        UINT uHttpSubError; // Allows for customization of error text based on srf specific errors.
        LPCSTR szHeader;    // the string that should appear in the http response header
        UINT uResId;        // the resource id of the string to send back as the body
    };


    // GetErrorText retrieves the http response header string
    // and a resource id of the response body for a given
    // http error code
    // uError: Http error code to retrieve information for
    // ppszHeader: pointer to LPCSTR that receives the response header string
    //          ppszHeader is optional
    // puResId: pointer to UINT that receives the response body resource id
    //          puResId is optional
    static BOOL GetErrorText(__in UINT uError, __in UINT uSubErr, __deref_out_opt LPCSTR *ppszHeader, __out_opt UINT *puResId) throw()
    {
        static const HTTP_ERROR_TEXT s_Errors[] =
        {
            { 200, SUBERR_NONE, "OK", 0 },
            { 201, SUBERR_NONE, "Created", 0 },
            { 202, SUBERR_NONE, "Accepted", 0 },
            { 203, SUBERR_NONE, "Non-Authoritative Information", 0 },
            { 204, SUBERR_NONE, "No Content", 0 },
            { 204, DBG_SUBERR_ALREADY_DEBUGGING, "Already being debugged by another user", 0},
            { 204, DBG_SUBERR_NOT_DEBUGGING, "Not currently debugging a process", 0},
            { 204, DBG_SUBERR_INVALID_SESSION, "Requested DebugSessionID does not match current DebugSessionID", 0},
            { 204, DBG_SUBERR_BAD_ID, "DebugSessionID corrupted or not provided", 0 },
            { 204, DBG_SUBERR_COCREATE, "Could not CoCreate the debugger", 0 },
            { 204, DBG_SUBERR_ATTACH, "Could not attach to process", 0 },
            { 205, SUBERR_NONE, "Reset Content", 0 },
            { 206, SUBERR_NONE, "Partial Content", 0 },
            { 300, SUBERR_NONE, "Multiple Choices", 0 },
            { 301, SUBERR_NONE, "Moved Permanently", 0 },
            { 302, SUBERR_NONE, "Found", 0 },
            { 303, SUBERR_NONE, "See Other", 0 },
            { 304, SUBERR_NONE, "Not Modified", 0 },
            { 305, SUBERR_NONE, "Use Proxy", 0 },
            { 306, SUBERR_NONE, "(Unused)", 0 },
            { 307, SUBERR_NONE, "Temporary Redirect", 0 },
            { 400, SUBERR_NONE, "Bad Request", IDS_ATLSRV_BAD_REQUEST },
            { 401, SUBERR_NONE, "Unauthorized", IDS_ATLSRV_AUTH_REQUIRED },
            { 402, SUBERR_NONE, "Payment Required", 0 },
            { 403, SUBERR_NONE, "Forbidden", IDS_ATLSRV_FORBIDDEN },
            { 404, SUBERR_NONE, "Not Found", IDS_ATLSRV_NOT_FOUND },
            { 405, SUBERR_NONE, "Method Not Allowed", 0 },
            { 406, SUBERR_NONE, "Not Acceptable", 0 },
            { 407, SUBERR_NONE, "Proxy Authentication Required", 0 },
            { 408, SUBERR_NONE, "Request Timeout", 0 },
            { 409, SUBERR_NONE, "Conflict", 0 },
            { 410, SUBERR_NONE, "Gone", 0 },
            { 411, SUBERR_NONE, "Length Required", 0 },
            { 412, SUBERR_NONE, "Precondition Failed", 0 },
            { 413, SUBERR_NONE, "Request Entity Too Long", 0 },
            { 414, SUBERR_NONE, "Request-URI Too Long", 0 },
            { 415, SUBERR_NONE, "Unsupported Media Type", 0 },
            { 416, SUBERR_NONE, "Requested Range Not Satisfiable", 0 },
            { 417, SUBERR_NONE, "Expectation Failed", 0 },
            { 500, SUBERR_NONE, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR },
            { 500, ISE_SUBERR_BADSRF, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_BADSRF },
            { 500, ISE_SUBERR_HNDLFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HNDLFAIL },
            { 500, ISE_SUBERR_SYSOBJFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_SYSOBJFAIL},
            { 500, ISE_SUBERR_READFILEFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_READFILEFAIL},
            { 500, ISE_SUBERR_LOADFILEFAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LOADFILEFAIL},
            { 500, ISE_SUBERR_LOADLIB, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LOADLIB},
            { 500, ISE_SUBERR_HANDLERIF, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HANDLERIF},
            { 500, ISE_SUBERR_OUTOFMEM, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_OUTOFMEM},
            { 500, ISE_SUBERR_UNEXPECTED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_UNEXPECTED},
            { 500, ISE_SUBERR_STENCIL_PARSE_FAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_STENCILPARSEFAIL},
            { 500, ISE_SUBERR_STENCIL_LOAD_FAIL, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_STENCILLOADFAIL},
            { 500, ISE_SUBERR_HANDLER_NOT_FOUND, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_HANDLERNOTFOUND},
            { 500, ISE_SUBERR_BAD_HANDLER_TAG, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_BADHANDLERTAG},
            { 500, ISE_SUBERR_LONGMETHODNAME, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LONGMETHODNAME},
            { 500, ISE_SUBERR_LONGHANDLERNAME, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_LONGHANDLERNAME},
            { 500, ISE_SUBERR_NO_HANDLER_TAG, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_NOHANDLERTAG},
            { 500, ISE_SUBERR_IMPERSONATIONFAILED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_IMPERSONATIONFAILED},
            { 500, ISE_SUBERR_ISAPISTARTUPFAILED, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_ISAPISTARTUPFAILED},
            { 500, ISE_SUBERR_SOAPNOSOAPACTION, "Internal Server Error", IDS_ATLSRV_SERVER_ERROR_SOAPNOSOAPACTION},

            { 501, SUBERR_NONE, "Not Implemented", IDS_ATLSRV_NOT_IMPLEMENTED },
            { 502, SUBERR_NONE, "Bad Gateway", IDS_ATLSRV_BAD_GATEWAY },
            { 503, SUBERR_NONE, "Service Unavailable", IDS_ATLSRV_SERVICE_NOT_AVAILABLE },
            { 504, SUBERR_NONE, "Gateway Timeout", 0 },
            { 505, SUBERR_NONE, "HTTP Version Not Supported", 0 },
        };

        // look for the error
        for(int i = 0; i < sizeof(s_Errors) / sizeof(s_Errors[0]); i++)
        {
            if((s_Errors[i].uHttpError == uError) && (s_Errors[i].uHttpSubError == uSubErr))
            {
                if(ppszHeader)
                    *ppszHeader = s_Errors[i].szHeader;
                if(puResId)
                    *puResId = s_Errors[i].uResId;
                return TRUE;
            }
        }

        // not found
        return FALSE;
    }
}; // CDefaultErrorProvider

template<class HttpUserErrorTextProvider>
void GetStatusHeader(__inout CStringA &strStatus, __in DWORD dwStatus, __in DWORD dwSubStatus, __in HttpUserErrorTextProvider* pErrorProvider, __out_opt UINT *puResId = NULL) throw(...)
{
    ATLENSURE(pErrorProvider != NULL);

    LPCSTR szHeadErr = NULL;
    // First, we check for the error text in the extension's user error text provider
    BOOL bRet = pErrorProvider->GetErrorText(dwStatus, dwSubStatus, &szHeadErr, puResId);
    if(!bRet)
    {
        szHeadErr = "";
    }

    char szBuf[512];
    Checked::itoa_s(dwStatus, szBuf, _countof(szBuf), 10);

    // add the space after the 3 digit response code
    szBuf[3] = ' ';
    szBuf[4] = '\0';
    strStatus.SetString(szBuf, 4);
    strStatus.Append(szHeadErr);
}

template<class HttpUserErrorTextProvider>
void RenderError(__in IHttpServerContext *pServerContext, __in DWORD dwStatus, __in DWORD dwSubStatus, __in HttpUserErrorTextProvider* pErrorProvider)
{
    ATLENSURE(pServerContext != NULL);
    ATLENSURE(pErrorProvider != NULL);
    _ATLTRY
    {
        UINT uResId = 0;

        CFixedStringT<CStringA, 256> strStatus;
        GetStatusHeader(strStatus, dwStatus, dwSubStatus, pErrorProvider, &uResId);
        pServerContext->SendResponseHeader(NULL, strStatus, FALSE);

        LPCSTR szBody = strStatus;
        DWORD dwBodyLen = strStatus.GetLength();
        CFixedStringT<CStringA, 1024> strBody;
        if(uResId)
        {
            // load the body string from a resource
            if(strBody.LoadString(uResId))
            {
                szBody = strBody;
                dwBodyLen = strBody.GetLength();
            }
        }

        pServerContext->WriteClient((void *) szBody, &dwBodyLen);
    }
    _ATLCATCHALL()
    {
        // last resort message when low on memory
        LPCSTR szError;
        BOOL bRes;
        bRes = CDefaultErrorProvider::GetErrorText(dwStatus, dwSubStatus, &szError, 0);
        if(!bRes)
            bRes = CDefaultErrorProvider::GetErrorText(dwStatus, SUBERR_NONE, &szError, 0);
        if(!bRes)
            bRes = CDefaultErrorProvider::GetErrorText(500, SUBERR_NONE, &szError, 0);
        if(!szError)
        {
            szError = "Unknown Error"; // last resort, can't localize
        }
        DWORD dwBodyLen = (DWORD) strlen(szError);
        pServerContext->WriteClient((void *) szError, &dwBodyLen);
    }
}

// Call this function to retrieve the full canonical physical path
// of a file relative to the current script.
//
// Returns TRUE on success, FALSE on error.
//
// szFile           A file path relative to the current script directory for which
//                  you are trying to retrieve the full path.
//
// szFullFileName   A caller-allocated buffer of at least MAX_PATH characters in length.
//                  On success, contains the the full canonical path of szFile.
//
// pServerContext   The context for the current request. The context is used to obtain the
//                  current script directory.
inline BOOL GetScriptFullFileName(
    __in LPCSTR szFile,
    __in_ecount(MAX_PATH) LPSTR szFullFileName,
    __in IHttpServerContext* pServerContext) throw(...)
{
    ATLENSURE(szFile != NULL);
    ATLASSERT(szFullFileName != NULL);
    ATLENSURE(pServerContext != NULL);

    char szTmpScriptPath[MAX_PATH];
    LPCSTR szTmp = pServerContext->GetScriptPathTranslated();

    if(!szTmp)
    {
        return FALSE;
    }

    if(!SafeStringCopy(szTmpScriptPath, szTmp))
    {
        // path is too long
        return FALSE;
    }

    CHAR *szScriptPath = szTmpScriptPath;

    LPSTR szBackslash;
    if(*szFile != '\\')
    {
        szBackslash = strrchr(szScriptPath, '\\');

        ATLASSERT(*(szScriptPath + strlen(szScriptPath)) != '\\');

        if(szBackslash)
            szBackslash++;
    }
    else
    {
        // handle case where szFile is of the form \directory\etc\etc
        szBackslash = strchr(szScriptPath, '\\');
    }

    if(szBackslash)
        *szBackslash = '\0';

    int nScriptPathLen = (int)(szBackslash ? strlen(szScriptPath) : 0);
    int nFileLen = (int) strlen(szFile);
    int newLen = nScriptPathLen + nFileLen;

    if((newLen < nScriptPathLen) || (newLen < nFileLen) || (newLen > MAX_PATH - 1))
    {
        return FALSE;
    }
    CHAR szTemp[MAX_PATH];
    if(nScriptPathLen)
    {
        Checked::memcpy_s(szTemp, MAX_PATH, szScriptPath, nScriptPathLen);
    }
    Checked::memcpy_s(szTemp + nScriptPathLen, MAX_PATH - nScriptPathLen, szFile, nFileLen);
    *(szTemp + newLen) = 0;

    return PathCanonicalizeA(szFullFileName, szTemp);
}

enum ATLSRV_STATE
{
    ATLSRV_STATE_BEGIN,     // The request has just arrived, and the type has not been determined
    ATLSRV_STATE_CONTINUE,  // The request is a continuation of an async request
    ATLSRV_STATE_DONE,      // The request is a continuation of an async request, but the server is done with it
    ATLSRV_STATE_CACHE_DONE // The request is the callback of a cached page
};

enum ATLSRV_REQUESTTYPE
{
    ATLSRV_REQUEST_UNKNOWN = -1, // The request type isn't known yet
    ATLSRV_REQUEST_STENCIL,     // The request is for a .srf file
    ATLSRV_REQUEST_DLL          // The request is for a .dll file
};

// Flags the InitRequest can return in dwStatus
#define  ATLSRV_INIT_USECACHE    1
#define  ATLSRV_INIT_USEASYNC    2
#define  ATLSRV_INIT_USEASYNC_EX 4 // required for use of NOFLUSH status

typedef HTTP_CODE(IRequestHandler::*PFnHandleRequest)(AtlServerRequest *pRequestInfo, IServiceProvider *pProvider);
typedef void (*PFnAsyncComplete)(AtlServerRequest *pRequestInfo, DWORD cbIO, DWORD dwError);

struct AtlServerRequest
{
    DWORD cbSize;							// For future compatibility
    IHttpServerContext *pServerContext;		// Necessary because it wraps the ECB
    ATLSRV_REQUESTTYPE dwRequestType;		// See the ATLSRV variables above
    // Indicates whether it was called through an .srf file or through a .dll file
    ATLSRV_STATE dwRequestState;			// See the ATLSRV variables above
    // Indicates what state of completion the request is in
    IRequestHandler *pHandler;				// Necessary because the callback (for async calls) must know where to
    // route the request
    HINSTANCE hInstDll;						// Necessary in order to release the dll properly (for async calls)
    IIsapiExtension *pExtension;			// Necessary to requeue the request (for async calls)
    IDllCache* pDllCache;					// Necessary to release the dll in async callback

    HANDLE hFile;
    HCACHEITEM hEntry;
    IFileCache* pFileCache;

    HANDLE m_hMutex;						// necessary to syncronize calls to HandleRequest
    // if HandleRequest could potientially make an
    // async call before returning. only used
    // if indicated with ATLSRV_INIT_USEASYNC_EX

    DWORD dwStartTicks;						// Tick count when the request was received
    EXTENSION_CONTROL_BLOCK *pECB;
    PFnHandleRequest pfnHandleRequest;
    PFnAsyncComplete pfnAsyncComplete;
    LPCSTR pszBuffer;						// buffer to be flushed asyncronously
    DWORD dwBufferLen;						// length of data in pszBuffer
    void* pUserData;						// value that can be used to pass user data between parent and child handlers
};

inline void _ReleaseAtlServerRequest(__inout AtlServerRequest* pRequest)
{
    ATLENSURE(pRequest != NULL);
    if(pRequest->pHandler)
        pRequest->pHandler->Release();
    if(pRequest->pServerContext)
        pRequest->pServerContext->Release();
    if(pRequest->pDllCache && pRequest->hInstDll)
        pRequest->pDllCache->ReleaseModule(pRequest->hInstDll);
    if(pRequest->m_hMutex)
        CloseHandle(pRequest->m_hMutex);
}

typedef BOOL (__stdcall *GETATLHANDLERBYNAME)(LPCSTR szHandlerName, IIsapiExtension *pExtension, IUnknown **ppHandler);
typedef BOOL (__stdcall *INITIALIZEATLHANDLERS)(IHttpServerContext*, IIsapiExtension*);
typedef void (__stdcall *UNINITIALIZEATLHANDLERS)();

// initial size of thread worker heap (per thread)
// The heap is growable.  The default initial is 16KB
#ifndef ATLS_WORKER_HEAP_SIZE
#define ATLS_WORKER_HEAP_SIZE 16384
#endif

class CIsapiWorker
{
public:
    typedef AtlServerRequest* RequestType;
    HANDLE m_hHeap;
#ifndef ATL_NO_SOAP
    CComPtr<ISAXXMLReader> m_spReader;
#endif

    CIsapiWorker() throw()
    {
        m_hHeap = NULL;
    }

    virtual ~CIsapiWorker() throw()
    {
        ATLASSUME(m_hHeap == NULL);
    }

    virtual BOOL Initialize(__inout __crt_typefix(IIsapiExtension*) void *pvParam)
    {
        IIsapiExtension* pExtension = (IIsapiExtension*) pvParam;
        ATLENSURE(pExtension);
        if(!(pExtension->OnThreadAttach()))
            return FALSE;

        m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, ATLS_WORKER_HEAP_SIZE, 0);
        if(!m_hHeap)
            return FALSE;
#ifndef ATL_NO_SOAP
        if(FAILED(m_spReader.CoCreateInstance(ATLS_SAXXMLREADER_CLSID, NULL, CLSCTX_INPROC_SERVER)))
        {
            ATLASSERT(FALSE);
            ATLTRACE(atlTraceISAPI, 0, _T("MSXML3 is not installed -- web services will not work."));
        }
#endif
        return pExtension->SetThreadWorker(this);
    }

    virtual void Terminate(__inout_opt __crt_typefix(IIsapiExtension*) void* pvParam) throw()
    {
        if(m_hHeap)
        {
            if(HeapDestroy(m_hHeap))
                m_hHeap = NULL;
            else
            {
                ATLASSERT(FALSE);
            }
        }

#ifndef ATL_NO_SOAP
        m_spReader.Release();
#endif
        if(pvParam != NULL)
            (static_cast<IIsapiExtension*>(pvParam))->OnThreadTerminate();
    }

    void Execute(__inout AtlServerRequest *pRequestInfo, __inout __crt_typefix(IIsapiExtension*) void *pvParam, __reserved OVERLAPPED *pOverlapped)
    {
        ATLENSURE(pRequestInfo != NULL);
        ATLENSURE(pvParam != NULL);
        (pOverlapped);    // unused
        ATLASSUME(m_hHeap != NULL);
        // any exceptions thrown at this point should have been caught in an
        // override of DispatchStencilCall. They will not be thrown out of this
        // function.
        _ATLTRY
        {
            (static_cast<IIsapiExtension*>(pvParam))->DispatchStencilCall(pRequestInfo);
        }
        _ATLCATCHALL()
        {
            ATLTRACE(_T("Warning. An uncaught exception was thrown from DispatchStencilCall\n"));
            ATLASSERT(FALSE);
        }
    }

    virtual BOOL GetWorkerData(DWORD /*dwParam*/, void ** /*ppvData*/) throw()
    {
        return FALSE;
    }
};

inline void _AtlGetScriptPathTranslated(
    __in LPCSTR szPathTranslated,
    __inout CFixedStringT<CStringA, MAX_PATH>& strScriptPathTranslated)
{
    ATLENSURE(szPathTranslated != NULL);
    LPCSTR szEnd = szPathTranslated;

    while(TRUE)
    {
        while(*szEnd != '.' && *szEnd != '\0')
            szEnd++;
        if(*szEnd == '\0')
            break;

        szEnd++;

        size_t nLen(0);
        if(!AsciiStrnicmp(szEnd, c_AtlDLLExtension + 1, ATLS_DLL_EXTENSION_LEN))
            nLen = ATLS_DLL_EXTENSION_LEN;
        else if(!AsciiStrnicmp(szEnd, c_AtlSRFExtension + 1, ATLS_EXTENSION_LEN))
            nLen = ATLS_EXTENSION_LEN;

        if(nLen)
        {
            szEnd += nLen;
            if(!*szEnd || *szEnd == '/' || *szEnd == '\\' || *szEnd == '?' || *szEnd == '#')
                break;
        }
    }

    DWORD dwResult = (DWORD)(szEnd - szPathTranslated);
    char *szScriptPathTranslated = NULL;
    ATLTRY(szScriptPathTranslated = strScriptPathTranslated.GetBuffer(dwResult));
    if(szScriptPathTranslated)
    {
        Checked::memcpy_s(szScriptPathTranslated, dwResult, szPathTranslated, dwResult);
        szScriptPathTranslated[dwResult] = '\0';
        strScriptPathTranslated.ReleaseBuffer(dwResult);
    }
}


struct CStencilState
{
    CStencilState() throw()
    {
        dwIndex = 0;
        locale = CP_ACP;
        pIncludeInfo = NULL;
        pParentInfo = NULL;
    }

    DWORD dwIndex;
    LCID locale;
    AtlServerRequest* pIncludeInfo;
    AtlServerRequest* pParentInfo;
};

class CWrappedServerContext:
    public IHttpServerContext
{
public:
    CComPtr<IHttpServerContext> m_spParent;

    CWrappedServerContext() throw()
    {
    }

    virtual ~CWrappedServerContext() throw()
    {
    }

    CWrappedServerContext(__in IHttpServerContext *pParent) throw()
    {
        m_spParent = pParent;
    }

    LPCSTR GetRequestMethod()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetRequestMethod();
    }

    LPCSTR GetQueryString()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetQueryString();
    }

    LPCSTR GetPathInfo()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetPathInfo();
    }

    LPCSTR GetScriptPathTranslated()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetScriptPathTranslated();
    }

    LPCSTR GetPathTranslated()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetPathTranslated();
    }

    DWORD GetTotalBytes()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetTotalBytes();
    }

    DWORD GetAvailableBytes()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetAvailableBytes();
    }

    BYTE *GetAvailableData()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetAvailableData();
    }

    LPCSTR GetContentType()
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetContentType();
    }

    __checkReturn BOOL GetServerVariable(__in_z LPCSTR pszVariableName, __out_ecount_part(*pdwSize, *pdwSize) LPSTR pvBuffer, __inout DWORD *pdwSize)
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetServerVariable(pszVariableName, pvBuffer, pdwSize);
    }

    __checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
    {
        ATLENSURE(m_spParent);
        return m_spParent->WriteClient(pvBuffer, pdwBytes);
    }

    __checkReturn BOOL AsyncWriteClient(__in_bcount(*pdwBytes) void * pvBuffer, __inout DWORD * pdwBytes)
    {
        ATLENSURE(m_spParent);
        return m_spParent->AsyncWriteClient(pvBuffer, pdwBytes);
    }

    __checkReturn BOOL ReadClient(__out_bcount_part(*pdwSize, *pdwSize) void * pvBuffer, __inout DWORD * pdwSize)
    {
        ATLENSURE(m_spParent);
        return m_spParent->ReadClient(pvBuffer, pdwSize);
    }

    __checkReturn BOOL AsyncReadClient(__out_bcount_part(*pdwSize, *pdwSize) void * pvBuffer, __inout DWORD * pdwSize)
    {
        ATLENSURE(m_spParent);
        return m_spParent->AsyncReadClient(pvBuffer, pdwSize);
    }

    __checkReturn BOOL SendRedirectResponse(__in LPCSTR pszRedirectUrl)
    {
        ATLENSURE(m_spParent);
        return m_spParent->SendRedirectResponse(pszRedirectUrl);
    }

    __checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
    {
        ATLENSURE(m_spParent);
        return m_spParent->GetImpersonationToken(pToken);
    }

    __checkReturn BOOL SendResponseHeader(__in LPCSTR pszHeader, __in LPCSTR pszStatusCode, __in BOOL fKeepConn)
    {
        ATLENSURE(m_spParent);
        return m_spParent->SendResponseHeader(pszHeader, pszStatusCode, fKeepConn);
    }

    __checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
    {
        ATLENSURE(m_spParent);
        return m_spParent->DoneWithSession(dwHttpStatusCode);
    }

    __checkReturn BOOL RequestIOCompletion(__in PFN_HSE_IO_COMPLETION pfn, DWORD * pdwContext)
    {
        ATLENSURE(m_spParent);
        return m_spParent->RequestIOCompletion(pfn, pdwContext);
    }

    BOOL TransmitFile(__in HANDLE hFile, __in_opt PFN_HSE_IO_COMPLETION pfn, void * pContext,
                      __in LPCSTR szStatusCode, __in DWORD dwBytesToWrite, __in DWORD dwOffset, __in_bcount_opt(dwHeadLen) void * pvHead,
                      __in DWORD dwHeadLen, __in_bcount_opt(dwTailLen) void * pvTail, __in DWORD dwTailLen, __in DWORD dwFlags)
    {
        ATLENSURE(m_spParent);
        return m_spParent->TransmitFile(hFile, pfn, pContext, szStatusCode,
                                        dwBytesToWrite, dwOffset, pvHead, dwHeadLen, pvTail, dwTailLen,
                                        dwFlags);
    }

    BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD* pdwLen)
    {
        ATLENSURE(m_spParent);
        return m_spParent->AppendToLog(szMessage, pdwLen);
    }

    BOOL MapUrlToPathEx(__in_bcount(dwLen) LPCSTR szLogicalPath, __in DWORD dwLen, __out HSE_URL_MAPEX_INFO *pumInfo)
    {
        ATLENSURE(m_spParent);
        return m_spParent->MapUrlToPathEx(szLogicalPath, dwLen, pumInfo);
    }
}; // class CWrappedServerContext

// Wraps the EXTENSION_CONTROL_BLOCK structure used by IIS to provide
// an ISAPI extension with information about the current request and
// access to the web server's functionality.
class CServerContext :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IHttpServerContext
{
public:
    BEGIN_COM_MAP(CServerContext)
    COM_INTERFACE_ENTRY(IHttpServerContext)
    END_COM_MAP()

    CServerContext() throw()
    {
        m_pECB = NULL;
        m_bHeadersHaveBeenSent = false;
    }
    virtual ~CServerContext() throw()
    {
    }

    void Initialize(__in EXTENSION_CONTROL_BLOCK *pECB)
    {
        ATLENSURE(pECB);
        m_pECB = pECB;

        // Initialize the translated script path
        _AtlGetScriptPathTranslated(GetPathTranslated(), m_strScriptPathTranslated);
    }

    // Returns a nul-terminated string that contains the HTTP method of the current request.
    // Examples of common HTTP methods include "GET" and "POST".
    // Equivalent to the REQUEST_METHOD server variable or EXTENSION_CONTROL_BLOCK::lpszMethod.
    LPCSTR GetRequestMethod()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpszMethod;
    }

    // Returns a nul-terminated string that contains the query information.
    // This is the part of the URL that appears after the question mark (?).
    // Equivalent to the QUERY_STRING server variable or EXTENSION_CONTROL_BLOCK::lpszQueryString.
    LPCSTR GetQueryString()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpszQueryString;
    }

    // Returns a nul-terminated string that contains the path of the current request.
    // This is the part of the URL that appears after the server name, but before the query string.
    // Equivalent to the PATH_INFO server variable or EXTENSION_CONTROL_BLOCK::lpszPathInfo.
    LPCSTR GetPathInfo()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpszPathInfo;
    }

    // Call this function to retrieve a nul-terminated string containing the physical path of the script.
    //
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the
    // buffer (including the nul-terminating byte).
    // The script path is the same as GetPathTranslated up to the first .srf or .dll.
    // For example, if GetPathTranslated returns "c:\inetpub\vcisapi\hello.srf\goodmorning",
    // then this function returns "c:\inetpub\vcisapi\hello.srf".
    LPCSTR GetScriptPathTranslated()
    {
        ATLASSUME(m_pECB);
        return m_strScriptPathTranslated;
    }


    // Returns a nul-terminated string that contains the translated path of the requested resource.
    // This is the path of the resource on the local server.
    // Equivalent to the PATH_TRANSLATED server variable or EXTENSION_CONTROL_BLOCK::lpszPathTranslated.
    LPCSTR GetPathTranslated()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpszPathTranslated;
    }

    // Returns the total number of bytes to be received from the client.
    // If this value is 0xffffffff, then there are four gigabytes or more of available data.
    // In this case, ReadClient or AsyncReadClient should be called until no more data is returned.
    // Equivalent to the CONTENT_LENGTH server variable or EXTENSION_CONTROL_BLOCK::cbTotalBytes.
    DWORD GetTotalBytes()
    {
        ATLENSURE(m_pECB);
        return m_pECB->cbTotalBytes;
    }

    // Returns the number of bytes available in the request buffer accessible via GetAvailableData.
    // If GetAvailableBytes returns the same value as GetTotalBytes, the request buffer contains the whole request.
    // Otherwise, the remaining data should be read from the client using ReadClient or AsyncReadClient.
    // Equivalent to EXTENSION_CONTROL_BLOCK::cbAvailable.
    DWORD GetAvailableBytes()
    {
        ATLENSURE(m_pECB);
        return m_pECB->cbAvailable;
    }

    // Returns a pointer to the request buffer containing the data sent by the client.
    // The size of the buffer can be determined by calling GetAvailableBytes.
    // Equivalent to EXTENSION_CONTROL_BLOCK::lpbData
    BYTE *GetAvailableData()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpbData;
    }

    // Returns a nul-terminated string that contains the content type of the data sent by the client.
    // Equivalent to the CONTENT_TYPE server variable or EXTENSION_CONTROL_BLOCK::lpszContentType.
    LPCSTR GetContentType()
    {
        ATLENSURE(m_pECB);
        return m_pECB->lpszContentType;
    }

    // Call this function to retrieve a nul-terminated string containing the value of the requested server variable.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    // Equivalent to  EXTENSION_CONTROL_BLOCK::GetServerVariable.
    __checkReturn BOOL GetServerVariable(
        __in LPCSTR pszVariableName,
        __out_ecount_part(*pdwSize, *pdwSize) LPSTR pvBuffer,
        __inout DWORD *pdwSize)
    {
        ATLENSURE(m_pECB);
        ATLASSERT(pszVariableName);
        ATLASSERT(pdwSize);

        if(pszVariableName && pdwSize)
        {
            return m_pECB->GetServerVariable(m_pECB->ConnID, (LPSTR) pszVariableName,
                                             pvBuffer, pdwSize);
        }
        return FALSE;
    }

    // Synchronously sends the data present in the given buffer to the client that made the request.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // Equivalent to EXTENSION_CONTROL_BLOCK::WriteClient(..., HSE_IO_SYNC).
    __checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
    {
        ATLENSURE(m_pECB);
        ATLASSERT(pvBuffer);
        ATLASSERT(pdwBytes);

        if(pvBuffer && pdwBytes)
        {
            return m_pECB->WriteClient(m_pECB->ConnID, pvBuffer, pdwBytes, HSE_IO_SYNC | HSE_IO_NODELAY);
        }
        return FALSE;
    }

    // Asynchronously sends the data present in the given buffer to the client that made the request.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // Equivalent to EXTENSION_CONTROL_BLOCK::WriteClient(..., HSE_IO_ASYNC).
    __checkReturn BOOL AsyncWriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
    {
        ATLENSURE(m_pECB);
        ATLASSERT(pvBuffer);
        ATLASSERT(pdwBytes);

        if(pvBuffer && pdwBytes)
        {
            return m_pECB->WriteClient(m_pECB->ConnID, pvBuffer, pdwBytes, HSE_IO_ASYNC | HSE_IO_NODELAY);
        }
        return FALSE;
    }

    // Call this function to synchronously read information from the body of the web client's HTTP request into the buffer supplied by the caller.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // Equivalent to EXTENSION_CONTROL_BLOCK::ReadClient.
    __checkReturn BOOL ReadClient(__out_ecount_part(*pdwSize, *pdwSize) void *pvBuffer, __inout DWORD *pdwSize)
    {
        ATLENSURE(m_pECB);
        ATLASSERT(pvBuffer);
        ATLASSERT(pdwSize);

        if(pvBuffer && pdwSize)
        {
            return m_pECB->ReadClient(m_pECB->ConnID, pvBuffer, pdwSize);
        }
        return FALSE;
    }

    // Call this function to asynchronously read information from the body of the web client's HTTP request into the buffer supplied by the caller.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // Equivalent to the HSE_REQ_ASYNC_READ_CLIENT server support function.
    __checkReturn BOOL AsyncReadClient(__out_bcount_part(*pdwSize, *pdwSize) void *pvBuffer, __inout DWORD *pdwSize)
    {
        // To call this function successfully someone has to have already
        // called RequestIOCompletion specifying the callback function
        // to be used for IO completion.
        ATLENSURE(m_pECB);
        ATLASSERT(pvBuffer);
        ATLASSERT(pdwSize);

        if(pvBuffer && pdwSize)
        {
            DWORD dwFlag = HSE_IO_ASYNC;
            return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                                 HSE_REQ_ASYNC_READ_CLIENT, pvBuffer, pdwSize,
                                                 &dwFlag);
        }
        return FALSE;
    }

    // Call this function to redirect the client to the specified URL.
    // The client receives a 302 (Found) HTTP status code.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_SEND_URL_REDIRECT_RESP server support function.
    __checkReturn BOOL SendRedirectResponse(__in LPCSTR pszRedirectUrl)
    {
        ATLENSURE(m_pECB);
        ATLENSURE(pszRedirectUrl);

        if(pszRedirectUrl)
        {
            DWORD dwSize = (DWORD) strlen(pszRedirectUrl);
            return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                                 HSE_REQ_SEND_URL_REDIRECT_RESP,
                                                 (void *) pszRedirectUrl, &dwSize, NULL);
        }
        return FALSE;
    }

    // Call this function to retrieve a handle to the impersonation token for this request.
    // An impersonation token represents a user context. You can use the handle in calls to ImpersonateLoggedOnUser or SetThreadToken.
    // Do not call CloseHandle on the handle.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_GET_IMPERSONATION_TOKEN server support function.
    __checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
    {
        ATLENSURE(m_pECB);
        if(pToken)
        {
            return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                                 HSE_REQ_GET_IMPERSONATION_TOKEN, pToken,
                                                 NULL, NULL);
        }
        return FALSE;
    }

    // Call this function to send an HTTP response header to the client including the HTTP status, server version, message time, and MIME version.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_SEND_RESPONSE_HEADER_EX server support function.
    __checkReturn BOOL SendResponseHeader(
        __in LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
        __in LPCSTR pszStatusCode = "200 OK",
        __in BOOL fKeepConn = FALSE)
    {
        ATLENSURE(m_pECB);

        if(m_bHeadersHaveBeenSent)
            return TRUE;

        HSE_SEND_HEADER_EX_INFO hex;
        hex.pszStatus = pszStatusCode;
        hex.pszHeader = pszHeader;
        hex.cchStatus = (DWORD)(pszStatusCode ? strlen(pszStatusCode) : 0);
        hex.cchHeader = (DWORD)(pszHeader ? strlen(pszHeader) : 0);
        hex.fKeepConn = fKeepConn;

        m_bHeadersHaveBeenSent = true;

        return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                             HSE_REQ_SEND_RESPONSE_HEADER_EX,
                                             &hex, NULL, NULL);
    }

    // Call this function to terminate the session for the current request.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_DONE_WITH_SESSION server support function.
    __checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
    {
        ATLENSURE(m_pECB);

        m_pECB->dwHttpStatusCode = dwHttpStatusCode;

        DWORD dwStatusCode = (dwHttpStatusCode >= 400) ? HSE_STATUS_ERROR : HSE_STATUS_SUCCESS;

        return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                             HSE_REQ_DONE_WITH_SESSION, &dwStatusCode, NULL, NULL);
    }

    // Call this function to set a special callback function that will be used for handling the completion of asynchronous I/O operations.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_IO_COMPLETION server support function.
    __checkReturn BOOL RequestIOCompletion(__in PFN_HSE_IO_COMPLETION pfn, DWORD *pdwContext)
    {
        ATLENSURE(m_pECB);
        ATLASSERT(pfn);

        if(pfn)
        {
            return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                                 HSE_REQ_IO_COMPLETION, pfn, NULL, pdwContext);
        }
        return FALSE;
    }

    // Call this function to transmit a file asynchronously to the client.
    // Returns TRUE on success, and FALSE on failure.
    // Equivalent to the HSE_REQ_TRANSMIT_FILE server support function.
    BOOL TransmitFile(
        __in HANDLE hFile,
        __in_opt PFN_HSE_IO_COMPLETION pfn,
        void *pContext,
        __in LPCSTR szStatusCode,
        __in DWORD dwBytesToWrite,
        __in DWORD dwOffset,
        __in_bcount_opt(dwHeadLen) void *pvHead,
        __in DWORD dwHeadLen,
        __in_bcount_opt(dwTailLen) void *pvTail,
        __in DWORD dwTailLen,
        __in DWORD dwFlags)
    {
        ATLENSURE(m_pECB);

        HSE_TF_INFO tf;
        tf.hFile = hFile;
        tf.BytesToWrite = dwBytesToWrite;
        tf.Offset = dwOffset;
        tf.pContext = pContext;
        tf.pfnHseIO = pfn;
        tf.pHead = pvHead;
        tf.HeadLength = dwHeadLen;
        tf.pTail = pvTail;
        tf.TailLength = dwTailLen;
        tf.pszStatusCode = szStatusCode;
        tf.dwFlags = dwFlags;
        return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                             HSE_REQ_TRANSMIT_FILE, &tf, NULL, NULL);
    }

    // Appends the string szMessage to the web server log for the current
    // request.
    // Returns TRUE on success, FALSE on failure.
    // Equivalent to the HSE_APPEND_LOG_PARAMETER server support function.
    BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD *pdwLen)
    {
        DWORD dwLen = 0;
        if(!pdwLen)
        {
            if(!szMessage)
            {
                return FALSE;
            }
            dwLen = (DWORD)strlen(szMessage);
        }
        else
        {
            dwLen = *pdwLen;
        }

        return m_pECB->ServerSupportFunction(m_pECB->ConnID,
                                             HSE_APPEND_LOG_PARAMETER, (void *)szMessage,
                                             &dwLen, NULL);
    }

    // Maps a logical Url Path to a physical path
    // Returns TRUE on success, FALSE on failure.
    // Equivalent to the HSE_REQ_MAP_URL_TO_PATH_EX server support function.
    // you can pass 0 for dwLen if szLogicalPath is null terminated
    BOOL MapUrlToPathEx(__in_bcount(dwLen) LPCSTR szLogicalPath, __in DWORD dwLen, __out HSE_URL_MAPEX_INFO *pumInfo)
    {
        ATLENSURE(m_pECB != NULL);
        if(dwLen == 0)
            dwLen = (DWORD) strlen(szLogicalPath);
        return m_pECB->ServerSupportFunction(m_pECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, (void *) szLogicalPath,
                                             &dwLen, (DWORD *) pumInfo);
    }

protected:
    // The pointer to the extension control block provided by IIS.
    EXTENSION_CONTROL_BLOCK *m_pECB;
    bool m_bHeadersHaveBeenSent;

    // The translated script path
    CFixedStringT<CStringA, MAX_PATH> m_strScriptPathTranslated;

}; // class CServerContext

class CPageCachePeer
{
public:

    struct PeerInfo
    {
        CStringA strHeader;
        CStringA strStatus;
    };

    static BOOL Add(__inout PeerInfo * pDest, __in PeerInfo * pSrc) throw()
    {
        _ATLTRY
        {
            PeerInfo *pIn = (PeerInfo *)pSrc;
            pDest->strHeader = pIn->strHeader;
            pDest->strStatus = pIn->strStatus;
            return TRUE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }

    static BOOL Remove(const PeerInfo * /*pDest*/) throw()
    {
        return TRUE;
    }
};


class CCacheServerContext :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CWrappedServerContext,
    public IPageCacheControl
{
private:

    CAtlTemporaryFile m_cacheFile;
    CComPtr<IFileCache> m_spCache;
    char m_szFullUrl[ATL_URL_MAX_URL_LENGTH + 1];
    FILETIME m_ftExpiration;
    BOOL m_bIsCached;
    CPageCachePeer::PeerInfo m_Headers;

public:

    BEGIN_COM_MAP(CCacheServerContext)
    COM_INTERFACE_ENTRY(IHttpServerContext)
    COM_INTERFACE_ENTRY(IPageCacheControl)
    END_COM_MAP()

    CCacheServerContext() throw()
    {
    }
    virtual ~CCacheServerContext() throw()
    {
    }

    BOOL Initialize(__in IHttpServerContext *pParent, __in IFileCache *pCache) throw()
    {
        ATLASSERT(pParent);
        ATLASSERT(pCache);

        if(pParent == NULL || pCache == NULL)
            return FALSE;

        m_spParent = pParent;
        m_spCache = pCache;

        if(FAILED(m_cacheFile.Create()))
            return FALSE;

        LPCSTR szPathInfo = pParent->GetPathInfo();
        LPCSTR szQueryString = pParent->GetQueryString();
        if(szPathInfo == NULL || szQueryString == NULL)
            return FALSE;

        LPSTR szTo = m_szFullUrl;
        int nSize = 0;
        while(*szPathInfo && nSize < ATL_URL_MAX_URL_LENGTH)
        {
            *szTo++ = *szPathInfo++;
            nSize++;
        }
        if(nSize >= ATL_URL_MAX_URL_LENGTH)
        {
            return FALSE;
        }
        *szTo++ = '?';
        nSize++;
        while(*szQueryString && nSize < ATL_URL_MAX_URL_LENGTH)
        {
            *szTo++ = *szQueryString++;
            nSize++;
        }
        if(nSize >= ATL_URL_MAX_URL_LENGTH)
        {
            return FALSE;
        }
        *szTo = '\0';

        memset(&m_ftExpiration, 0x00, sizeof(FILETIME));
        m_bIsCached = TRUE;

        return TRUE;
    }

    // IPageCacheControl methods
    HRESULT GetExpiration(__out FILETIME *pftExpiration) throw()
    {
        ATLASSERT(pftExpiration);
        if(!pftExpiration)
            return E_INVALIDARG;

        *pftExpiration = m_ftExpiration;

        return S_OK;
    }

    HRESULT SetExpiration(__in FILETIME ftExpiration) throw()
    {
        m_ftExpiration = ftExpiration;

        return S_OK;
    }

    __checkReturn BOOL IsCached() throw()
    {
        return m_bIsCached;
    }

    BOOL Cache(__in BOOL bCache) throw()
    {
        BOOL bRet = m_bIsCached;
        m_bIsCached = bCache;
        return bRet;
    }

    __checkReturn BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
    {
        ATLENSURE(pvBuffer);
        ATLENSURE(pdwBytes);

        if(S_OK != m_cacheFile.Write(pvBuffer, *pdwBytes))
            return FALSE;
        ATLENSURE(m_spParent);
        return m_spParent->WriteClient(pvBuffer, pdwBytes);
    }

    __checkReturn BOOL DoneWithSession(__in DWORD dwHttpStatusCode)
    {
        ATLENSURE(m_spParent);

        _ATLTRY
        {
            if(m_bIsCached)
            {
                CT2CA strFileName(m_cacheFile.TempFileName());
                m_cacheFile.HandsOff();
                m_spCache->AddFile(m_szFullUrl, strFileName, &m_ftExpiration, &m_Headers, NULL);
            }
            else
                m_cacheFile.Close();
        }
        _ATLCATCHALL()
        {
            m_cacheFile.Close();
        }

        return m_spParent->DoneWithSession(dwHttpStatusCode);
    }

    __checkReturn BOOL GetImpersonationToken(__out HANDLE * pToken)
    {
        ATLTRACE(atlTraceISAPI, 0, _T("Getting impersonation token for cached page")
                 _T(" -- Caching a page that requires special privileges to build is a possible security problem. ")
                 _T("Future hits may get a cached page without going through the security checks done during the page creation process"));
        ATLENSURE(m_spParent);
        ATLASSERT(pToken);
        return m_spParent->GetImpersonationToken(pToken);
    }

    __checkReturn BOOL AppendToLog(__in LPCSTR szMessage, __in_opt DWORD* pdwLen)
    {
        ATLTRACE(atlTraceISAPI, 0, _T("Logging on cached page -- future hits will not log"));
        ATLENSURE(m_spParent);
        return m_spParent->AppendToLog(szMessage, pdwLen);
    }

    __checkReturn BOOL SendResponseHeader(
        __in LPCSTR pszHeader = "Content-Type: text/html\r\n\r\n",
        __in LPCSTR pszStatusCode = "200 OK",
        __in BOOL fKeepConn = FALSE)
    {
        ATLENSURE(m_spParent);

        m_Headers.strHeader = pszHeader;
        m_Headers.strStatus = pszStatusCode;

        return m_spParent->SendResponseHeader(pszHeader, pszStatusCode, fKeepConn);
    }

    // The methods below this point are actions that should not be performed on cached
    // pages, as they will not behave correctly.
    __checkReturn BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/)
    {
        // Asynchronous calls will not work
        ATLASSERT(FALSE);
        return FALSE;
    }

    __checkReturn BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        // Nobody should be reading from this client if the page is being cached
        // Also, only GET's are cached anyway
        ATLASSERT(FALSE);
        return FALSE;
    }

    __checkReturn BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }

    __checkReturn BOOL SendRedirectResponse(LPCSTR /*pszRedirectUrl*/)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }


    __checkReturn BOOL RequestIOCompletion(PFN_HSE_IO_COMPLETION /*pfn*/, DWORD * /*pdwContext*/)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }

    __checkReturn BOOL TransmitFile(
        HANDLE /*hFile*/,
        PFN_HSE_IO_COMPLETION /*pfn*/,
        void * /*pContext*/,
        LPCSTR /*szStatusCode*/,
        DWORD /*dwBytesToWrite*/,
        DWORD /*dwOffset*/,
        void * /*pvHead*/,
        DWORD /*dwHeadLen*/,
        void * /*pvTail*/,
        DWORD /*dwTailLen*/,
        DWORD /*dwFlags*/)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }
};


// This class represents a collection of validation failures.
// Use this class in combination with CValidateObject to validate
// forms, cookies, or query strings and build up a collection of
// failures. If appropriate, use the information in the collection
// to return detailed responses to the client to help them correct the failures.


class CValidateContext
{
public:
    enum { ATL_EMPTY_PARAMS_ARE_FAILURES = 0x00000001 };

    CValidateContext(__in DWORD dwFlags = 0) throw()
    {
        m_bFailures = false;
        m_dwFlags = dwFlags;
    }

    bool SetResultAt(__in LPCSTR szName, __in DWORD type)
    {
        _ATLTRY
        {
            if(!VALIDATION_SUCCEEDED(type) ||
            (type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES)))
                m_bFailures = true;

            return TRUE == m_results.SetAt(szName, type);

        }
        _ATLCATCHALL()
        {
        }

        return false;
    }

    // Call this function to add a validation result to the collection managed by this object.
    // Each result is identified by a name and the type of result that occurred.
    // The result codes are the VALIDATION_ codes defined at the top of this file.
    // The bOnlyFailure parameter below is used to only allow failure results to
    // be added to the list of failures. The reason you'd want to do this is that
    // success codes should be the common case in validation routines so you can
    // use bOnlyFailures to limit the number of allocations by this class's base
    // map for mapping success results if you don't care about iterating successes.

    bool AddResult(__in LPCSTR szName, __in DWORD type, __in bool bOnlyFailures = true) throw()
    {
        _ATLTRY
        {
            if(!VALIDATION_SUCCEEDED(type) ||
            (type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES)))
                m_bFailures = true;

            if(!bOnlyFailures)
                return TRUE == m_results.Add(szName, type); // add everything

            else if(bOnlyFailures &&
            (!VALIDATION_SUCCEEDED(type) ||
            (type == VALIDATION_S_EMPTY && (m_dwFlags & ATL_EMPTY_PARAMS_ARE_FAILURES))))
                return TRUE == m_results.Add(szName, type); // only add failures
        }
        _ATLCATCHALL()
        {
        }

        return false;
    }

    // Returns true if there are no validation failures in the collection,
    // returns false otherwise.
    __checkReturn bool ParamsOK() throw()
    {
        return !m_bFailures;
    }

    // Returns the number of validation results in the collection.
    __checkReturn int GetResultCount() throw()
    {
        return m_results.GetSize();
    }

    // Call this function to retrieve the name and type of a
    // validation result based on its index in the collection.
    // Returns true on success, false on failure.
    //
    // i        The index of a result managed by this collection.
    //
    // strName  On success, the name of the result with index i.
    //
    // type     On success, the type of the result with index i.
    __checkReturn bool GetResultAt(__in int i, __out CStringA& strName, __out DWORD& type) throw()
    {
        if(i >= 0 && i < m_results.GetSize())
        {
            _ATLTRY
            {
                strName = m_results.GetKeyAt(i);
                type = m_results.GetValueAt(i);
            }
            _ATLCATCHALL()
            {
                return false;
            }
            return true;
        }
        return false;
    }

    DWORD m_dwFlags;
protected:
    CSimpleMap<CStringA, DWORD> m_results;
    bool m_bFailures;
}; // CValidateContext



class CAtlValidator
{
public:
    template <class T, class TCompType>
    static DWORD Validate(
        __in T value,
        __in TCompType nMinValue,
        __in TCompType nMaxValue) throw()
    {
        DWORD dwRet = VALIDATION_S_OK;
        if(value < static_cast<T>(nMinValue))
            dwRet = VALIDATION_E_LENGTHMIN;
        else if(value > static_cast<T>(nMaxValue))
            dwRet = VALIDATION_E_LENGTHMAX;
        return dwRet;
    }

    static DWORD Validate(__in LPCSTR pszValue, __in int nMinChars, __in int nMaxChars) throw()
    {
        DWORD dwRet = VALIDATION_S_OK;
        if(!pszValue)
        {
            return VALIDATION_E_FAIL;
        }
        int nChars = (int) strlen(pszValue);
        if(nChars < nMinChars)
            dwRet = VALIDATION_E_LENGTHMIN;
        else if(nChars > nMaxChars)
            dwRet = VALIDATION_E_LENGTHMAX;
        return dwRet;
    }
    static DWORD Validate(__in double dblValue, __in double dblMinValue, __in double dblMaxValue) throw()
    {
        DWORD dwRet = VALIDATION_S_OK;
        if(dblValue < (dblMinValue - ATL_EPSILON))
            dwRet = VALIDATION_E_LENGTHMIN;
        else if(dblValue > (dblMaxValue + ATL_EPSILON))
            dwRet = VALIDATION_E_LENGTHMAX;
        return dwRet;
    }
};

// This class provides functions for retrieving and validating named values.
//
// The named values are expected to be provided in string form by the class used as
// the template parameter. CValidateObject provides the means of
// retrieving these values converted to data types chosen by you. You can validate the values
// by specifying a range for numeric values or by specifying a minimum and maximum length
// for string values.
//
// Call one of the Exchange overloads to retrieve a named value converted to your chosen data type.
// Call one of the Validate overloads to retrieve a named value converted to your chosen data type
// and validated against a minimum and maximum value or length supplied by you.
//
// To add validation functionality to the class TLookupClass, derive that class from CValidateObject<TLookupClass>
// and provide a Lookup function that takes a name as a string and returns the corresponding value
// also as a string:
//      LPCSTR Lookup(LPCSTR szName);
template < class TLookupClass, class TValidator = CAtlValidator >
class CValidateObject
{
public:
    // Exchange Routines

    // Call this function to retrieve a named value converted to your chosen data type.
    // Returns one of the following validation status codes:
    //      VALIDATION_S_OK             The named value was found and could be converted successfully
    //      VALIDATION_S_EMPTY          The name was present, but the value was empty
    //      VALIDATION_E_PARAMNOTFOUND  The named value was not found
    //      VALIDATION_E_INVALIDPARAM   The name was present, but the value could not be converted to the requested data type
    //      VALIDATION_E_FAIL           An unspecified error occurred
    // Pass a pointer to a validation context object if you want to add
    // failures to the collection managed by that object.
    template <class T>
    ATL_NOINLINE __checkReturn DWORD Exchange(
        __in LPCSTR szParam,
        __out T* pValue,
        __inout_opt CValidateContext *pContext = NULL) const throw()
    {
        DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
        if(pValue)
        {
            _ATLTRY
            {
                const TLookupClass *pT = static_cast<const TLookupClass*>(this);
                LPCSTR szValue = pT->Lookup(szParam);
                if(szValue)
                {
                    if(*szValue == '\0')
                        dwRet = VALIDATION_S_EMPTY;
                    else
                    {
                        dwRet = ConvertNumber(szValue, pValue);
                    }
                }
            }
            _ATLCATCHALL()
            {
                return VALIDATION_E_FAIL;
            }
        }
        else
            dwRet = VALIDATION_E_FAIL; // invalid input

        if(pContext)
            pContext->AddResult(szParam, dwRet);
        return dwRet;
    }

    template<>
    ATL_NOINLINE __checkReturn DWORD Exchange(
        __in LPCSTR szParam,
        __out_opt CString* pstrValue,
        __in_opt CValidateContext *pContext) const throw()
    {
        _ATLTRY
        {
            LPCSTR pszValue = NULL;
            DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
            if(pstrValue)
            {
                dwRet = Exchange(szParam, &pszValue, pContext);
                if(VALIDATION_SUCCEEDED(dwRet) && pstrValue != NULL)
                    *pstrValue = CA2T(pszValue);
            }
            else
            {
                dwRet = VALIDATION_E_FAIL; // invalid input
                if(pContext)
                    pContext->AddResult(szParam, dwRet);
            }

            return dwRet;
        }
        _ATLCATCHALL()
        {
            return VALIDATION_E_FAIL;
        }
    }

    template<>
    ATL_NOINLINE __checkReturn DWORD Exchange(
        __in LPCSTR szParam,
        __deref_out_opt LPCSTR* ppszValue,
        __inout_opt CValidateContext *pContext) const throw()
    {
        DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
        if(ppszValue)
        {
            _ATLTRY
            {
                *ppszValue = NULL;
                const TLookupClass *pT = static_cast<const TLookupClass*>(this);
                LPCSTR szValue = pT->Lookup(szParam);
                if(szValue)
                {
                    if(*szValue == '\0')
                        dwRet = VALIDATION_S_EMPTY;
                    else
                    {
                        *ppszValue = szValue;
                        dwRet = VALIDATION_S_OK;
                    }
                }
            }
            _ATLCATCHALL()
            {
                return VALIDATION_E_FAIL;
            }
        }
        else
            dwRet = VALIDATION_E_FAIL; // invalid input

        if(pContext)
            pContext->AddResult(szParam, dwRet);
        return dwRet;
    }

    template<>
    ATL_NOINLINE __checkReturn DWORD Exchange(
        __in LPCSTR szParam,
        __out GUID* pValue,
        __inout_opt CValidateContext *pContext) const throw()
    {
        DWORD dwRet = VALIDATION_E_PARAMNOTFOUND;
        if(pValue)
        {
            _ATLTRY
            {
                const TLookupClass *pT = static_cast<const TLookupClass*>(this);
                LPCSTR szValue = pT->Lookup(szParam);
                if(szValue)
                {
                    if(*szValue == '\0')
                        dwRet = VALIDATION_S_EMPTY;
                    else
                    {
                        if(S_OK != CLSIDFromString(CA2W(szValue), pValue))
                        {
                            dwRet = VALIDATION_E_INVALIDPARAM;
                        }
                        else
                            dwRet = VALIDATION_S_OK;
                    }
                }
            }
            _ATLCATCHALL()
            {
                return VALIDATION_E_FAIL;
            }
        }
        else
            dwRet = VALIDATION_E_FAIL; // invalid input

        if(pContext)
            pContext->AddResult(szParam, dwRet);
        return dwRet;
    }

    template<>
    ATL_NOINLINE __checkReturn DWORD Exchange(
        __in LPCSTR szParam,
        __out bool* pbValue,
        __inout_opt CValidateContext *pContext) const throw()
    {
        DWORD dwRet = VALIDATION_S_OK;
        if(pbValue)
        {
            _ATLTRY
            {
                const TLookupClass *pT = static_cast<const TLookupClass*>(this);
                LPCSTR szValue = pT->Lookup(szParam);
                *pbValue = false;
                if(szValue)
                {
                    if(*szValue != '\0')
                        *pbValue = true;
                }
            }
            _ATLCATCHALL()
            {
                return VALIDATION_E_FAIL;
            }
        }
        else
            dwRet = VALIDATION_E_FAIL; // invalid input

        if(pContext)
            pContext->AddResult(szParam, dwRet);

        return dwRet;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out ULONGLONG *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        char *pEnd = NULL;
        ULONGLONG n = 0;
        errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
        if(pEnd == szVal || errnoValue == ERANGE)
        {
            return VALIDATION_E_INVALIDPARAM;
        }
        *pnVal = n;
        return VALIDATION_S_OK;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out LONGLONG *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        char *pEnd = NULL;
        LONGLONG n = 0;
        errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
        if(pEnd == szVal || errnoValue == ERANGE)
        {
            return VALIDATION_E_INVALIDPARAM;
        }
        *pnVal = n;
        return VALIDATION_S_OK;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out double *pdblVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pdblVal);
        if(!pdblVal)
            return VALIDATION_E_FAIL;
        char *pEnd = NULL;
        double d = 0.0;
        errno_t errnoValue = AtlStrToNum(&d, szVal, &pEnd);
        if(pEnd == szVal || errnoValue == ERANGE)
        {
            return VALIDATION_E_INVALIDPARAM;
        }
        *pdblVal = d;
        return VALIDATION_S_OK;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out int *pnVal) const throw()
    {
        return ConvertNumber(szVal, (long*)pnVal);
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned int *pnVal) const throw()
    {
        return ConvertNumber(szVal, (unsigned long*)pnVal);
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out long *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        char *pEnd = NULL;
        long n = 0;
        errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
        if(pEnd == szVal || errnoValue == ERANGE)
        {
            return VALIDATION_E_INVALIDPARAM;
        }
        *pnVal = n;
        return VALIDATION_S_OK;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned long *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        char *pEnd = NULL;
        unsigned long n = 0;
        errno_t errnoValue = AtlStrToNum(&n, szVal, &pEnd, 10);
        if(pEnd == szVal || errnoValue == ERANGE)
        {
            return VALIDATION_E_INVALIDPARAM;
        }
        *pnVal = n;
        return VALIDATION_S_OK;
    }

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out short *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        long nVal = 0;
        DWORD dwRet = ConvertNumber(szVal, &nVal);
        if(dwRet == VALIDATION_S_OK)
        {
            // clamp to the size of a short
            if(nVal <= SHRT_MAX &&
               nVal >= SHRT_MIN)
            {
                *pnVal = (short)nVal;
            }
            else
            {
                dwRet = VALIDATION_E_INVALIDPARAM;
            }
        }
        return dwRet;
    };

    __checkReturn DWORD ConvertNumber(__in LPCSTR szVal, __out unsigned short *pnVal) const throw()
    {
        if(!szVal)
            return VALIDATION_E_FAIL;

        ATLASSERT(pnVal);
        if(!pnVal)
            return VALIDATION_E_FAIL;
        unsigned long nVal = 0;
        DWORD dwRet = ConvertNumber(szVal, &nVal);
        if(dwRet == VALIDATION_S_OK)
        {
            // clamp to the size of a short
            if(nVal <= USHRT_MAX &&
               nVal >= 0)
            {
                *pnVal = (unsigned short)nVal;
            }
            else
            {
                dwRet = VALIDATION_E_INVALIDPARAM;
            }
        }
        return dwRet;
    };

    // Call this function to retrieve a named value converted to your chosen data type
    // and validated against a minimum and maximum value or length supplied by you.
    //
    // Returns one of the following validation status codes:
    //      VALIDATION_S_OK             The named value was found and could be converted successfully
    //      VALIDATION_S_EMPTY          The name was present, but the value was empty
    //      VALIDATION_E_PARAMNOTFOUND  The named value was not found
    //      VALIDATION_E_INVALIDPARAM   The name was present, but the value could not be converted to the requested data type
    //      VALIDATION_E_LENGTHMIN      The name was present and could be converted to the requested data type, but the value was too small
    //      VALIDATION_E_LENGTHMAX      The name was present and could be converted to the requested data type, but the value was too large
    //      VALIDATION_E_FAIL           An unspecified error occurred
    //
    // Validate can be used to convert and validate name-value pairs
    // such as those associated with HTTP requests (query string, form fields, or cookie values).
    // The numeric specializations validate the minimum and maximum value.
    // The string specializations validate the minimum and maximum length.
    //
    // Pass a pointer to a validation context object if you want to add
    // failures to the collection managed by that object.
    //
    // Note that you can validate the value of a parameter without
    // storing its value by passing NULL for the second parameter. However
    // if you pass NULL for the second parameter, make sure you cast the NULL to a
    // type so that the compiler will call the correct specialization of Validate.
    template <class T, class TCompType>
    ATL_NOINLINE __checkReturn DWORD Validate(
        __in LPCSTR Param,
        __out_opt T *pValue,
        __in TCompType nMinValue,
        __in TCompType nMaxValue,
        __inout_opt CValidateContext *pContext = NULL) const throw()
    {
        T value;
        DWORD dwRet = Exchange(Param, &value, pContext);
        if(dwRet == VALIDATION_S_OK)
        {
            if(pValue)
                *pValue = value;
            dwRet = TValidator::Validate(value, nMinValue, nMaxValue);
            if(pContext && dwRet != VALIDATION_S_OK)
                pContext->AddResult(Param, dwRet);
        }
        else if(dwRet == VALIDATION_S_EMPTY &&
                !IsNullByType(nMinValue))
        {
            dwRet = VALIDATION_E_LENGTHMIN;
            if(pContext)
            {
                pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
            }
        }

        return dwRet;
    }

    // Specialization for strings. Comparison is for number of characters.
    template<>
    ATL_NOINLINE __checkReturn DWORD Validate(
        __in LPCSTR Param,
        __deref_opt_out LPCSTR* ppszValue,
        __in int nMinChars,
        __in int nMaxChars,
        __inout_opt CValidateContext *pContext) const throw()
    {
        LPCSTR pszValue = NULL;
        DWORD dwRet = Exchange(Param, &pszValue, pContext);
        if(dwRet == VALIDATION_S_OK)
        {
            if(ppszValue)
                *ppszValue = pszValue;
            dwRet = TValidator::Validate(pszValue, nMinChars, nMaxChars);
            if(pContext && dwRet != VALIDATION_S_OK)
                pContext->AddResult(Param, dwRet);
        }
        else if(dwRet == VALIDATION_S_EMPTY &&
                nMinChars > 0)
        {
            dwRet = VALIDATION_E_LENGTHMIN;
            if(pContext)
            {
                pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
            }
        }


        return dwRet;
    }

    // Specialization for CString so caller doesn't have to cast CString
    template<>
    ATL_NOINLINE __checkReturn DWORD Validate(
        __in LPCSTR Param,
        __out_opt CString* pstrValue,
        __in int nMinChars,
        __in int nMaxChars,
        __inout_opt CValidateContext *pContext) const throw()
    {
        _ATLTRY
        {
            LPCSTR szValue;
            DWORD dwRet = Validate(Param, &szValue, nMinChars, nMaxChars, pContext);
            if(pstrValue && dwRet == VALIDATION_S_OK)
                *pstrValue = szValue;
            return dwRet;
        }
        _ATLCATCHALL()
        {
            return VALIDATION_E_FAIL;
        }
    }

    // Specialization for doubles, uses a different comparison.
    template<>
    ATL_NOINLINE __checkReturn DWORD Validate(
        __in LPCSTR Param,
        __out_opt double* pdblValue,
        __in double dblMinValue,
        __in double dblMaxValue,
        __inout_opt CValidateContext *pContext) const throw()
    {
        double dblValue;
        DWORD dwRet = Exchange(Param, &dblValue, pContext);
        if(dwRet == VALIDATION_S_OK)
        {
            if(pdblValue)
                *pdblValue = dblValue;
            dwRet = TValidator::Validate(dblValue, dblMinValue, dblMaxValue);
            if(pContext && dwRet != VALIDATION_S_OK)
                pContext->AddResult(Param, dwRet);
        }
        else if(dwRet == VALIDATION_S_EMPTY &&
                (dblMinValue < -ATL_EPSILON ||
                 dblMinValue > ATL_EPSILON))
        {
            dwRet = VALIDATION_E_LENGTHMIN;
            if(pContext)
            {
                pContext->SetResultAt(Param, VALIDATION_E_LENGTHMIN);
            }
        }
        return dwRet;
    }
};

// Cookies provide a way for a server to store a small amount of data on a client
// and have that data returned to it on each request the client makes.
// Use this class to represent a cookie to be sent from the server to a client
// or to represent a cookie that has been returned by a client to the originating server.
//
// At the HTTP level, a cookie is an application-defined name-value pair
// plus some standard attribute-value pairs that describe the way in which the user agent (web browser)
// should interact with the cookie. The HTTP format of a cookie is described in RFC 2109.
//
// The CCookie class provides methods to set and get the application-defined name and value
// as well as methods for the standard attributes. In addition, CCookie provides an abstraction
// on top of the application-defined value that allows it to be treated as a collection of name-value
// pairs if that model makes sense to you. Cookies with a single value are known as single-valued cookies.
// Cookies whose value consists of name-value pairs are known as multi-valued cookies or dictionary cookies.
//
// You can set the name of a cookie by calling SetName or using the appropriate constructor.
// The name of a cookie can be 0 or more characters.
//
// You can set the value of a cookie by calling SetValue or using the appropriate constructor.
// If the cookie has a value set, it is a single-valued cookie and attempts to add a name-value pair will fail.
// You can remove the value of a cookie by calling SetValue(NULL).
//
// You can add a name-value pair to a cookie by calling AddValue.
// If the cookie has any name-value pairs, it is a multi-valued cookie and attempts to set the primary value will fail.
// You can remove all the name-value pairs of a cookie by calling RemoveAllValues.
//
// Class CCookie follows the same rules for creating cookies as ASP does.
class CCookie :
    public CValidateObject<CCookie>
{
    typedef CAtlMap < CStringA, CStringA, CStringElementTraits<CStringA>,
            CStringElementTraits<CStringA> > mapType;

    const static DWORD ATLS_MAX_HTTP_DATE = 64;

public:
    // Constructs a named cookie.
    CCookie(__in LPCSTR szName) throw(...)
    {
        ATLENSURE(SetName(szName));
    }

    // Constructs a single-valued cookie.
    CCookie(__in LPCSTR szName, __in_opt LPCSTR szValue) throw(...)
    {
        ATLENSURE(SetName(szName));
        ATLENSURE(SetValue(szValue));
    }

    CCookie(__in const CCookie& thatCookie) throw(...)
    {
        Copy(thatCookie);
    }

    CCookie& operator=(__in const CCookie& thatCookie) throw(...)
    {
        if(this != &thatCookie)
        {
            return Copy(thatCookie);
        }
        return *this;
    }

    CCookie() throw()
    {

    }

    __checkReturn BOOL IsEmpty() const throw()
    {
        return m_strName.IsEmpty();
    }

    // Call this function to set the name of this cookie.
    // Returns TRUE on success, FALSE on failure.
    // The name of a cookie cannot contain whitespace, semicolons or commas.
    // The name should not begin with a dollar sign ($) since such names are reserved for future use.
    __checkReturn BOOL SetName(__in LPCSTR szName) throw()
    {
        _ATLTRY
        {
            if(szName && *szName)
            {
                m_strName = szName;
                return TRUE;
            }
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to retrieve the name of this cookie.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL GetName(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) const throw()
    {
        return CopyCString(m_strName, szBuff, pdwSize);
    }

    // Call this function to retrieve the name of this cookie.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL GetName(__out CStringA &strName) const throw()
    {
        _ATLTRY
        {
            strName = m_strName;
            return TRUE;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to set the value of this cookie.
    // Returns TRUE on success, FALSE on failure.
    // Will fail if the cookie is multi-valued.
    // Pass NULL to remove the cookie's value.
    __checkReturn BOOL SetValue(__in_opt LPCSTR szValue) throw()
    {
        _ATLTRY
        {
            if(m_Values.GetCount())
                return FALSE; //already dictionary values in the cookie

            if(!szValue)
                m_strValue.Empty();
            else
                m_strValue = szValue;

            return TRUE;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to retrieve the value of this cookie.
    // Returns TRUE on success, FALSE on failure.
    // Returns TRUE if there is no value or the value is of zero length.
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetValue(__out_ecount(*pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) const throw()
    {
        return CopyCString(m_strValue, szBuff, pdwSize);
    }

    // Call this function to retrieve the value of this cookie.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL GetValue(__out CStringA &strValue) const throw()
    {
        _ATLTRY
        {
            strValue = m_strValue;
            return TRUE;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to add a name-value pair to the cookie.
    // Returns TRUE on success, FALSE on failure.
    // Will fail if the cookie is single-valued.
    // If the named value is already present in the cookie, calling this function
    // will modify the current value, otherwise a new name-value pair is added to the cookie.
    // Call RemoveValue or RemoveAllValues to remove the name-value pairs
    // added by this function.
    __checkReturn BOOL AddValue(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
    {
        if(m_strValue.GetLength())
            return FALSE;
        _ATLTRY
        {
            return m_Values.SetAt(szName, szValue) != NULL;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to modify a name-value pair associated with the cookie.
    // Returns TRUE on success, FALSE on failure.
    // Will fail if the cookie is single-valued.
    // This function just calls AddValue so the name-value pair will be added if not already present.
    // Use this function instead of AddValue to document the intentions of your call.
    __checkReturn BOOL ModifyValue(__in LPCSTR szName, __in LPCSTR szValue) throw()
    {
        return AddValue(szName, szValue);
    }

    // Call this function to remove a name-value pair from the collection managed by this cookie.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL RemoveValue(__in LPCSTR szName) throw()
    {
        return m_Values.RemoveKey(szName);
    }

    // Call this function to remove all the name-value pairs from the collection managed by this cookie.
    void RemoveAllValues() throw()
    {
        m_Values.RemoveAll();
    }

    // Call this function to add an attribute-value pair to the collection of attributes for this cookie.
    // Returns TRUE on success, FALSE on failure.
    // This function is equivalent to calling ModifyAttribute.
    // Both functions will add the attribute if not already present or
    // change its value if it has already been applied to the cookie.
    __checkReturn BOOL AddAttribute(__in LPCSTR szName, __in LPCSTR szValue) throw()
    {
        if(!szName || !*szName || !szValue)
            return FALSE;

        _ATLTRY
        {
            return (m_Attributes.SetAt(szName, szValue) != NULL);
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;

    }

    // Call this function to modify an attribute-value pair associated with the cookie.
    // Returns TRUE on success, FALSE on failure.
    // This function is equivalent to calling AddAttribute.
    // Both functions will add the attribute if not already present or
    // change its value if it has already been applied to the cookie.
    __checkReturn BOOL ModifyAttribute(__in LPCSTR szName, __in LPCSTR szValue) throw()
    {
        return AddAttribute(szName, szValue);
    }

    // Call this function to remove an attribute-value pair from the collection of attributes managed by this cookie.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL RemoveAttribute(__in LPCSTR szName) throw()
    {
        return m_Attributes.RemoveKey(szName);
    }

    // Call this function to remove all the attribute-value pairs from the collection of attributes managed by this cookie.
    void RemoveAllAttributes() throw()
    {
        m_Attributes.RemoveAll();
    }


    // Call this function to set the Comment attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Comment attribute allows a web server to document its
    // intended use of a cookie. This information may be displayed
    // by supporting browsers so that the user of the web site can
    // decide whether to initiate or continue a session with this cookie.
    // This attribute is optional.
    // Version 1 attribute.
    __checkReturn BOOL SetComment(__in LPCSTR szComment) throw()
    {
        BOOL bRet = SetVersion(1);
        if(bRet)
            bRet = AddAttribute("comment", szComment);
        return bRet;
    }

    // Call this function to set the CommentUrl attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The CommentUrl attribute allows a web server to document its intended
    // use of a cookie via a URL that the user of the web site can navigate to.
    // The URL specified here should not send further cookies to the client to
    // avoid frustrating the user.
    // This attribute is optional.
    // Version 1 attribute.
    __checkReturn BOOL SetCommentUrl(__in LPCSTR szUrl) throw()
    {
        BOOL bRet = SetVersion(1);
        if(bRet)
            bRet = AddAttribute("commenturl", szUrl);
        return bRet;
    }

    // Call this function to add or remove the Discard attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Discard attribute does not have a value.
    // Call SetDiscard(TRUE) to add the Discard attribute
    // or SetDiscard(FALSE) to remove the Discard attribute.
    // Setting the Discard attribute tells a web browser that it should
    // discard this cookie when the browser exits regardless of the
    // value of the Max-Age attribute.
    // This attribute is optional.
    // When omitted, the default behavior is that the Max-Age attribute
    // controls the lifetime of the cookie.
    // Version 1 attribute.
    __checkReturn BOOL SetDiscard(__in BOOL bDiscard) throw()
    {
        BOOL bRet = FALSE;
        LPCSTR szKey = "Discard";
        bRet = SetVersion(1);
        if(bRet)
        {
            if(bDiscard == 0)
            {
                bRet = m_Attributes.RemoveKey(szKey);
            }
            else
            {
                _ATLTRY
                {
                    bRet = m_Attributes.SetAt(szKey, " ") != 0;
                }
                _ATLCATCHALL()
                {
                    bRet = FALSE;
                }
            }
        }
        return bRet;
    }

    // Call this function to set the Domain attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Domain attribute is used to indicate the domain to which the current
    // cookie applies. Browsers should only send cookies back to the relevant domains.
    // This attribute is optional.
    // When omitted, the default behavior is for
    // browsers to use the full domain of the server originating the cookie. You can
    // set this attribute value explicitly if you want to share cookies among several servers.
    // Version 0 & Version 1 attribute.
    __checkReturn BOOL SetDomain(__in LPCSTR szDomain) throw()
    {
        BOOL bRet = SetVersion(1);
        if(bRet)
            bRet = AddAttribute("domain", szDomain);
        return bRet;
    }

    // Call this function to set the Max-Age attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The value of the Max-Age attribute is a lifetime in seconds for the cookie.
    // When the time has expired, compliant browsers will discard this cookie
    // (if they haven't already done so as a result of the Discard attribute).
    // If Max-Age is set to zero, the browser discards the cookie immediately.
    // This attribute is the Version 1 replacement for the Expires attribute.
    // This attribute is optional.
    // When omitted, the default behavior is for browsers to discard cookies
    // when the user closes the browser.
    // Version 1 attribute.
    __checkReturn BOOL SetMaxAge(__in UINT nMaxAge) throw()
    {
        BOOL bRet = FALSE;
        bRet = SetVersion(1);
        if(bRet)
        {
            CHAR buff[20];
            if(0 == _itoa_s(nMaxAge, buff, _countof(buff), 10))
            {
                bRet = AddAttribute("max-age", buff);
            }
        }
        return bRet;
    }

    // Call this function to set the Path attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Path attribute specifies the subset of URLs to which this cookie applies.
    // Only URLs that contain that path are allowed to read or modify the cookie.
    // This attribute is optional.
    // When omitted the default behavior is for browsers to treat the path of a cookie
    // as the path of the request URL that generated the Set-Cookie response, up to,
    // but not including, the right-most /.
    // Version 0 & Version 1 attribute.
    __checkReturn BOOL SetPath(__in LPCSTR szPath) throw()
    {
        BOOL bRet = SetVersion(1);
        if(bRet)
            bRet = AddAttribute("path", szPath);
        return bRet;
    }

    // Call this function to set the Port attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Port attribute specifies the port to which this cookie applies.
    // Only URLs accessed via that port are allowed to read or modify the cookie.
    // This attribute is optional.
    // When omitted the default behavior is for browsers to return the cookie via any port.
    // Version 1 attribute.
    __checkReturn BOOL SetPort(__in LPCSTR szPort) throw()
    {
        BOOL bRet = SetVersion(1);
        if(bRet)
            bRet = AddAttribute("port", szPort);
        return bRet;
    }

    // Call this function to add or remove the Secure attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Secure attribute does not have a value.
    // Call SetSecure(TRUE) to add the Secure attribute
    // or SetSecure(FALSE) to remove the Secure attribute.
    // Setting the Secure attribute tells a browser that it should
    // transmit the cookie to the web server only via secure means such as HTTPS.
    // This attribute is optional.
    // When omitted, the default behavior is that the cookie
    // will be sent via unsecured protocols.
    // Version 0 & Version 1 attribute.
    __checkReturn BOOL SetSecure(__in BOOL bSecure) throw()
    {
        BOOL bRet = FALSE;
        LPCSTR szKey = "secure";
        bRet = SetVersion(1);
        if(bRet)
        {
            if(bSecure == 0)
            {
                bRet = m_Attributes.RemoveKey(szKey);
            }
            else
            {
                _ATLTRY
                {
                    bRet = m_Attributes.SetAt(szKey, " ") != 0;
                }
                _ATLCATCHALL()
                {
                    bRet = FALSE;
                }
            }
        }
        return bRet;
    }

    // Call this function to set the Version attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // This attribute is required for Version 1 cookies by RFC 2109 and must have a value of 1.
    // However, you do not need to call SetVersion explicitly from your own code unless you need to
    // force RFC 2109 compliance. CCookie will automatically set this attribute whenever
    // you use a Version 1 attribute in your cookie.
    // Version 1 attribute.
    __checkReturn BOOL SetVersion(__in UINT nVersion) throw()
    {
        BOOL bRet = FALSE;
        CHAR buff[20];
        if(0 == _itoa_s(nVersion, buff, _countof(buff), 10))
        {
            bRet = AddAttribute("version", buff);
        }
        return bRet;
    }

    // Call this function to set the Expires attribute of the cookie.
    // Returns TRUE on success, FALSE on failure.
    // The Expires attribute specifies an absolute date and time at which this cookie
    // should be discarded by web browsers. Pass a SYSTEMTIME holding a Greenwich Mean Time (GMT)
    // value or a string in the following format:
    //      Wdy, DD-Mon-YY HH:MM:SS GMT
    // This attribute is optional.
    // When omitted, the default behavior is for browsers to discard cookies
    // when the user closes the browser.
    // This attribute has been superceded in Version 1 by the Max-Age attribute,
    // but you should continue to use this attribute for Version 0 clients.
    // Version 0 attribute.
    __checkReturn BOOL SetExpires(__in LPCSTR szExpires) throw()
    {
        return AddAttribute("expires", szExpires);
    }

    __checkReturn BOOL SetExpires(__in const SYSTEMTIME &st) throw()
    {
        _ATLTRY
        {
            CFixedStringT<CStringA, ATLS_MAX_HTTP_DATE> strTime;
            SystemTimeToHttpDate(st, strTime);
            return SetExpires(strTime);
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to look up the value of a name-value pair applied to this cookie.
    // Returns the requested value if present or NULL if the name was not found.
    __checkReturn LPCSTR Lookup(__in_opt LPCSTR szName = NULL) const throw()
    {
        if(IsEmpty())
            return NULL;

        if(m_strValue.GetLength())
        {
            ATLASSERT(szName == NULL);
            return m_strValue;
        }

        if(m_Values.GetCount())
        {
            ATLENSURE_RETURN_VAL(szName, NULL);
            const mapType::CPair *pPair = NULL;
            ATLTRY(pPair = m_Values.Lookup(szName));
            if(pPair)
                return (LPCSTR)pPair->m_value;
        }

        return NULL;
    }

    // Call this function to clear the cookie of all content
    // including name, value, name-value pairs, and attributes.
    void Empty() throw()
    {
        m_strName.Empty();
        m_strValue.Empty();
        m_Attributes.RemoveAll();
        m_Values.RemoveAll();
    }

    // Call this function to create a CCookie from a buffer.
    // The passed in buffer contains a cookie header retrieved
    // from the HTTP_COOKIE request variable
    bool ParseValue(__in const char *pstart)
    {
        ATLASSERT(pstart);
        if(!pstart || *pstart == '\0')
            return false;

        // could be just a value or could be an array of name=value pairs
        LPCSTR pEnd = pstart;
        LPCSTR pStart = pstart;
        CStringA name, value;

        while(*pEnd != '\0')
        {
            while(*pEnd && *pEnd != '=' && *pEnd != '&')
                pEnd++;

            if(*pEnd == '\0' || *pEnd == '&')
            {
                if(pEnd > pStart)
                    CopyToCString(value, pStart, pEnd);
                SetValue(value);
                if(*pEnd == '&')
                {
                    pEnd++;
                    pStart = pEnd;
                    continue;
                }
                return true; // we're done;
            }
            else if(*pEnd == '=')
            {
                // we have name=value
                if(pEnd > pStart)
                {
                    CopyToCString(name, pStart, pEnd);
                }
                else
                {
                    pEnd++;
                    pStart = pEnd;
                    break;
                }

                // skip '=' and go for value
                pEnd++;
                pStart = pEnd;
                while(*pEnd && *pEnd != '&' && *pEnd != '=')
                    pEnd++;
                if(pEnd > pStart)
                    CopyToCString(value, pStart, pEnd);

                ATLENSURE(AddValue(name, value));

                if(*pEnd != '\0')
                    pEnd++;
                pStart = pEnd;

            }
        }

        return true;
    }

    // Call this function to render this cookie
    // into a buffer. Returns TRUE on success, FALSE on failure.
    // On entry, pdwLen should point to a DWORD that indicates
    // the size of the buffer in bytes. On exit, the DWORD contains
    // the number of bytes transferred or available to be transferred
    // into the buffer (including the nul-terminating byte). On
    // success, the buffer will contain the correct HTTP
    // representation of the current cookie suitable for sending to
    // a client as the body of a Set-Cookie header.
    __success(return == true) ATL_NOINLINE __checkReturn BOOL Render(__out_ecount_part_opt(*pdwLen, *pdwLen) LPSTR szCookieBuffer, __inout DWORD *pdwLen) const throw()
    {
        if(!pdwLen)
            return FALSE;
        CStringA strCookie;
        CStringA name, value;
        DWORD dwLenBuff = *pdwLen;
        *pdwLen = 0;

        // A cookie must have a name!
        if(!m_strName.GetLength())
        {
            *pdwLen = 0;
            return FALSE;
        }
        _ATLTRY
        {
            strCookie = m_strName;
            int nValSize = (int) m_Values.GetCount();
            if(nValSize)
            {
                strCookie += '=';
                POSITION pos = m_Values.GetStartPosition();
                for(int i = 0; pos; i++)
                {
                    m_Values.GetNextAssoc(pos, name, value);
                    strCookie += name;
                    if(value.GetLength())
                    {
                        strCookie += '=';
                        strCookie += value;
                    }
                    if(i <= nValSize - 2)
                        strCookie += '&';
                }
            }
            else
            {
                strCookie += '=';
                if(m_strValue.GetLength())
                    strCookie += m_strValue;
            }

            CStringA strAttributes;
            if(!RenderAttributes(strAttributes))
                return FALSE;
            if(strAttributes.GetLength() > 0)
            {
                strCookie += "; ";
                strCookie += strAttributes;
            }

            DWORD dwLenCookie = strCookie.GetLength() + 1;
            *pdwLen = dwLenCookie;
            if(!szCookieBuffer)
                return TRUE; // caller just wanted the length

            // see if buffer is big enough
            if(dwLenCookie > dwLenBuff)
                return FALSE; //buffer wasn't big enough

            // copy the buffer
            Checked::strcpy_s(szCookieBuffer, *pdwLen, strCookie);
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
        return TRUE;
    }

    POSITION GetFirstAttributePos() const throw()
    {
        return m_Attributes.GetStartPosition();
    }

    const CStringA& GetNextAttributeName(__inout POSITION& pos) const throw()
    {
        return m_Attributes.GetNextKey(pos);
    }

    const CStringA& GetAttributeValueAt(__in POSITION pos) const throw()
    {
        return m_Attributes.GetValueAt(pos);
    }

    BOOL GetNextAttrAssoc(__inout POSITION& pos, __out CStringA& key,
                          __out CStringA& val) const throw()
    {
        _ATLTRY
        {
            m_Attributes.GetNextAssoc(pos, key, val);
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
        return TRUE;
    }

    POSITION GetFirstValuePos() const throw()
    {
        return m_Values.GetStartPosition();
    }

    const CStringA& GetNextValueName(__inout POSITION& pos) const throw()
    {
        return m_Values.GetNextKey(pos);
    }

    const CStringA& GetValueAt(__in POSITION pos) const throw()
    {
        return m_Values.GetValueAt(pos);
    }

    BOOL GetNextValueAssoc(__inout POSITION& pos, __out CStringA& key,
                           __out CStringA& val) const throw()
    {
        _ATLTRY
        {
            m_Values.GetNextAssoc(pos, key, val);
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
        return TRUE;
    }

protected:
// Implementation
    BOOL RenderAttributes(__out CStringA& strAttributes) const throw()
    {
        _ATLTRY
        {
            strAttributes.Empty();

            POSITION pos = m_Attributes.GetStartPosition();
            CStringA key, val;
            for(int i = 0; pos; i++)
            {
                if(i)
                    strAttributes += ";";
                m_Attributes.GetNextAssoc(pos, key, val);
                strAttributes += key;
                strAttributes += '=';
                strAttributes += val;
            }
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
        return TRUE;
    }
private:
    CCookie& Copy(__in const CCookie& thatCookie) throw(...)
    {
        m_strName = thatCookie.m_strName;
        m_strValue = thatCookie.m_strValue;
        POSITION pos = NULL;
        CStringA strName, strValue;
        if(!thatCookie.m_Attributes.IsEmpty())
        {
            pos = thatCookie.m_Attributes.GetStartPosition();
            while(pos)
            {
                thatCookie.m_Attributes.GetNextAssoc(pos, strName, strValue);
                m_Attributes.SetAt(strName, strValue);
            }
        }
        if(!thatCookie.m_Values.IsEmpty())
        {
            strName.Empty();
            strValue.Empty();
            pos = thatCookie.m_Values.GetStartPosition();
            while(pos)
            {
                thatCookie.m_Values.GetNextAssoc(pos, strName, strValue);
                m_Values.SetAt(strName, strValue);
            }
        }
        return *this;
    }

    // Call this function to copy a substring to a CString reference and ensure nul-termination.
    void CopyToCString(__out CStringA& string, __in_ecount(pEnd - pStart) LPCSTR pStart, __in LPCSTR pEnd) throw(...)
    {
        ATLENSURE(pStart != NULL);
        ATLENSURE(pEnd != NULL);

        string.SetString(pStart, (int)(pEnd - pStart));
        string.Trim();
    }


public:
    // These are implementation only, use at your own risk!
    // Map of attribute-value pairs applied to this cookie.
    mapType m_Attributes;

    // Map of name-value pairs applied to this cookie.
    mapType m_Values;

    // The name of this cookie.
    CStringA m_strName;

    // The value of this cookie.
    CStringA m_strValue;

};  // class CCookie

class CSessionCookie : public CCookie
{
public:
    CSessionCookie() throw(...)
    {
        if(!SetName(SESSION_COOKIE_NAME) ||
           !SetPath("/"))
            AtlThrow(E_OUTOFMEMORY);
    }

    CSessionCookie(LPCSTR szSessionID) throw(...)
    {
        if(!SetName(SESSION_COOKIE_NAME) ||
           !SetPath("/") ||
           !SetSessionID(szSessionID))
            AtlThrow(E_OUTOFMEMORY);
    }

    BOOL SetSessionID(LPCSTR szSessionID) throw()
    {
        ATLASSERT(szSessionID && szSessionID[0]);
        return SetValue(szSessionID);
    }
}; // class CSessionCookie

template<>
class CElementTraits< CCookie > :
    public CElementTraitsBase< CCookie >
{
public:
    typedef const CCookie& INARGTYPE;
    typedef CCookie& OUTARGTYPE;

    static ULONG Hash(__in INARGTYPE cookie)
    {
        return CStringElementTraits<CStringA>::Hash(cookie.m_strName);
    }

    static bool CompareElements(__in INARGTYPE cookie1, __in INARGTYPE cookie2)
    {
        return(cookie1.m_strName == cookie2.m_strName);
    }

    static int CompareElementsOrdered(__in INARGTYPE cookie1, __in INARGTYPE cookie2)
    {
        return(cookie1.m_strName.Compare(cookie2.m_strName));
    }
};


///////////////////////////////////////////////////////////////////////////////
// Request and response classes and support functions


// This class is a wrapper for CAtlMap that allows maps to be chained.
// It simply adds a bool that tells whether or not a map shares values
template < typename K, typename V, typename KTraits = CElementTraits<K>, typename VTraits = CElementTraits<V> >
class CHttpMap
{
private:

#ifdef ATL_HTTP_PARAM_MULTIMAP
    typedef CRBMultiMap<K, V, KTraits, VTraits> MAPTYPE;
#else
    typedef CAtlMap<K, V, KTraits, VTraits> MAPTYPE;
#endif // ATL_HTTP_PARAM_MULTIMAP

public:

    typedef typename KTraits::INARGTYPE KINARGTYPE;
    typedef typename KTraits::OUTARGTYPE KOUTARGTYPE;
    typedef typename VTraits::INARGTYPE VINARGTYPE;
    typedef typename VTraits::OUTARGTYPE VOUTARGTYPE;

    typedef typename MAPTYPE::CPair CPair;

private:

    bool m_bShared;

    MAPTYPE m_map;

public:

    CHttpMap() throw()
        : m_bShared(false)
    {
    }

    virtual ~CHttpMap()
    {
    }

    inline bool IsShared() const throw()
    {
        return m_bShared;
    }

    inline void SetShared(__in bool bShared) throw()
    {
        m_bShared = bShared;
    }

    //
    // exposed lookup and iteration functionality
    //

    inline size_t GetCount() const throw()
    {
        return m_map.GetCount();
    }

    inline bool IsEmpty() const throw()
    {
        return m_map.IsEmpty();
    }

    inline POSITION GetStartPosition() const throw()
    {
#ifdef ATL_HTTP_PARAM_MULTIMAP
        return m_map.GetHeadPosition();
#else
        return m_map.GetStartPosition();
#endif // ATL_HTTP_PARAM_MULTIMAP
    }

    // Lookup wrappers
    bool Lookup(__in KINARGTYPE key, __out VOUTARGTYPE value) const throw()
    {
        _ATLTRY
        {
#ifdef ATL_HTTP_PARAM_MULTIMAP
            CPair *p = Lookup(key);
            if(p != NULL)
            {
                value = p->m_value;
                return true;
            }
            return false;
#else
            return m_map.Lookup(key, value);
#endif // ATL_HTTP_PARAM_MULTIMAP
        }
        _ATLCATCHALL()
        {
            return false;
        }
    }

    const CPair* Lookup(__in KINARGTYPE key) const throw()
    {
#ifdef ATL_HTTP_PARAM_MULTIMAP
        POSITION pos = m_map.FindFirstWithKey(key);
        if(pos != NULL)
        {
            return m_map.GetAt(pos);
        }
        return NULL;
#else
        return m_map.Lookup(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
    }

    CPair* Lookup(__in KINARGTYPE key) throw()
    {
#ifdef ATL_HTTP_PARAM_MULTIMAP
        POSITION pos = m_map.FindFirstWithKey(key);
        if(pos != NULL)
        {
            return m_map.GetAt(pos);
        }
        return NULL;
#else
        return m_map.Lookup(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
    }

    // iteration wrappers
    void GetNextAssoc(__inout POSITION& pos, __out KOUTARGTYPE key, __out VOUTARGTYPE value) const throw(...)
    {
        m_map.GetNextAssoc(pos, key, value);
    }

    const CPair* GetNext(__inout POSITION& pos) const throw()
    {
        return m_map.GetNext(pos);
    }

    CPair* GetNext(__inout POSITION& pos) throw()
    {
        return m_map.GetNext(pos);
    }

    const K& GetNextKey(__inout POSITION& pos) const throw()
    {
        return m_map.GetNextKey(pos);
    }

    const V& GetNextValue(__inout POSITION& pos) const throw()
    {
        return m_map.GetNextValue(pos);
    }

    V& GetNextValue(__inout POSITION& pos) throw()
    {
        return m_map.GetNextValue(pos);
    }

    void GetAt(__in POSITION pos, __out KOUTARGTYPE key, __out VOUTARGTYPE value) const throw(...)
    {
        return m_map.GetAt(pos, key, value);
    }

    CPair* GetAt(__in POSITION pos) throw()
    {
        return m_map.GetAt(pos);
    }

    const CPair* GetAt(__in POSITION pos) const throw()
    {
        return m_map.GetAt(pos);
    }

    const K& GetKeyAt(__in POSITION pos) const throw()
    {
        return m_map.GetKeyAt(pos);
    }

    const V& GetValueAt(__in POSITION pos) const throw()
    {
        return m_map.GetValueAt(pos);
    }

    V& GetValueAt(__in POSITION pos) throw()
    {
        return m_map.GetValueAt(pos);
    }

    // modification wrappers
    POSITION SetAt(__in KINARGTYPE key, __in_opt VINARGTYPE value) throw(...)
    {
#ifdef ATL_HTTP_PARAM_MULTIMAP
        return m_map.Insert(key, value);
#else
        return m_map.SetAt(key, value);
#endif // ATL_HTTP_PARAM_MULTIMAP
    }

    bool RemoveKey(__in KINARGTYPE key) throw()
    {
#ifdef ATL_HTTP_PARAM_MULTIMAP
        return (m_map.RemoveKey(key) != 0);
#else
        return m_map.RemoveKey(key);
#endif // ATL_HTTP_PARAM_MULTIMAP
    }

    virtual void RemoveAll()
    {
        m_map.RemoveAll();
    }
};

// This class is a wrapper for CHttpMap that assumes it's values are pointers that
// should be deleted on RemoveAll
template < typename K, typename V, typename KTraits = CElementTraits<K>, typename VTraits = CElementTraits<V> >
class CHttpPtrMap : public CHttpMap<K, V, KTraits, VTraits>
{
public:
    typedef CHttpMap<K, V, KTraits, VTraits> Base;

    void RemoveAll()
    {
        if(!IsShared())
        {
            POSITION pos = GetStartPosition();
            while(pos)
            {
                GetNextValue(pos)->Free();
            }
        }
        Base::RemoveAll();
    }

    ~CHttpPtrMap()
    {
        RemoveAll();
    }
};

// This class represents a collection of request parameters - the name-value pairs
// found, for example, in a query string or in the data provided when a form is submitted to the server.
// Call Parse to build the collection from a string of URL-encoded data.
// Use the standard collection methods of the CHttpMap base class to retrieve the
// decoded names and values.
// Use the methods of the CValidateObject base class to validate the parameters.
class CHttpRequestParams :
#if (defined(ATL_HTTP_PARAM_MAP_CASEINSENSITIVE))
    public CHttpMap<CStringA, CStringA, CStringElementTraitsI<CStringA>, CStringElementTraitsI<CStringA> >,
#else
    public CHttpMap<CStringA, CStringA, CStringElementTraits<CStringA>, CStringElementTraits<CStringA> >,
#endif
    public CValidateObject<CHttpRequestParams>
{
public:
#if (defined(ATL_HTTP_PARAM_MAP_CASEINSENSITIVE))
    typedef CHttpMap<CStringA, CStringA, CStringElementTraitsI<CStringA>, CStringElementTraitsI<CStringA> > BaseMap;
#else
    typedef CHttpMap<CStringA, CStringA, CStringElementTraits<CStringA>, CStringElementTraits<CStringA> > BaseMap;
#endif

    LPCSTR Lookup(__in LPCSTR szName) const throw()
    {
        _ATLTRY
        {
            if(!szName)
                return NULL;

            const CPair *p = BaseMap::Lookup(szName);
            if(p)
            {
                return p->m_value;
            }
        }
        _ATLCATCHALL()
        {
        }
        return NULL;
    }

    // Call this function to build a collection of name-value pairs from a string of URL-encoded data.
    // Returns TRUE on success, FALSE on failure.
    // URL-encoded data:
    //      Each name-value pair is separated from the next by an ampersand (&)
    //      Each name is separated from its corresponding value by an equals signs (=)
    //      The end of the data to be parsed is indicated by a nul character (\0) or a pound symbol (#)
    //      A plus sign (+) in the input will be decoded as a space
    //      A percent sign (%) in the input signifies the start of an escaped octet.
    //          The next two digits represent the hexadecimal code of the character.
    //          For example, %21 is the escaped encoding for the US-ASCII exclamation mark and will be decoded as !.
    // Common sources of URL-encoded data are query strings and the bodies of POST requests with content type of application/x-www-form-urlencoded.
    //
    // Parse and Render are complementary operations.
    // Parse creates a collection from a string.
    // Render creates a string from a collection.
    ATL_NOINLINE __checkReturn BOOL Parse(__inout LPSTR szQueryString) throw()
    {
        while(szQueryString && *szQueryString)
        {
            LPSTR szUrlCurrent = szQueryString;
            LPSTR szName = szUrlCurrent;
            LPSTR szPropValue;

            while(*szQueryString)
            {
                if(*szQueryString == '=')
                {
                    szQueryString++;
                    break;
                }
                if(*szQueryString == '&')
                {
                    break;
                }
                if(*szQueryString == '+')
                    *szUrlCurrent = ' ';
                else if(*szQueryString == '%')
                {
                    // if there is a % without two characters
                    // at the end of the url we skip it
                    if(*(szQueryString + 1) && *(szQueryString + 2))
                    {
                        short nFirstDigit = AtlHexValue(szQueryString[1]);
                        short nSecondDigit = AtlHexValue(szQueryString[2]);

                        if(nFirstDigit < 0 || nSecondDigit < 0)
                        {
                            break;
                        }
                        *szUrlCurrent = static_cast<CHAR>(16 * nFirstDigit + nSecondDigit);
                        szQueryString += 2;
                    }
                    else
                        *szUrlCurrent = '\0';
                }
                else
                    *szUrlCurrent = *szQueryString;

                szQueryString++;
                szUrlCurrent++;
            }

            if(*szUrlCurrent == '&')
            {
                *szUrlCurrent++ = '\0';
                szQueryString++;
                szPropValue = "";
            }
            else
            {
                if(*szUrlCurrent)
                    *szUrlCurrent++ = '\0';

                // we have the property name
                szPropValue = szUrlCurrent;
                while(*szQueryString && *szQueryString != '#')
                {
                    if(*szQueryString == '&')
                    {
                        szQueryString++;
                        break;
                    }
                    if(*szQueryString == '+')
                        *szUrlCurrent = ' ';
                    else if(*szQueryString == '%')
                    {
                        // if there is a % without two characters
                        // at the end of the url we skip it
                        if(*(szQueryString + 1) && *(szQueryString + 2))
                        {
                            short nFirstDigit = AtlHexValue(szQueryString[1]);
                            short nSecondDigit = AtlHexValue(szQueryString[2]);

                            if(nFirstDigit < 0 || nSecondDigit < 0)
                            {
                                break;
                            }
                            *szUrlCurrent = static_cast<CHAR>(16 * nFirstDigit + nSecondDigit);
                            szQueryString += 2;
                        }
                        else
                            *szUrlCurrent = '\0';
                    }
                    else
                        *szUrlCurrent = *szQueryString;
                    szQueryString++;
                    szUrlCurrent++;
                }
                // we have the value
                *szUrlCurrent = '\0';
                szUrlCurrent++;
            }

            _ATLTRY
            {
                SetAt(szName, szPropValue);
            }
            _ATLCATCHALL()
            {
                return FALSE;
            }
        }
        return TRUE;
    }

    // Call this function to render the map of names and values into a buffer as a URL-encoded string.
    // Returns TRUE on success, FALSE on failure.
    // On entry, pdwLen should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    // On success, the buffer will contain the correct URL-encoded representation of the current object
    // suitable for sending to a server as a query string or in the body of a form.
    // URL-encoding:
    //      Each name-value pair is separated from the next by an ampersand (&)
    //      Each name is separated from its corresponding value by an equals signs (=)
    //      A space is encoded as a plus sign (+).
    //      Other unsafe characters (as determined by AtlIsUnsafeUrlChar) are encoded as escaped octets.
    //      An escaped octet is a percent sign (%) followed by two digits representing the hexadecimal code of the character.
    //
    // Parse and Render are complementary operations.
    // Parse creates a collection from a string.
    // Render creates a string from a collection.
    ATL_NOINLINE __checkReturn BOOL Render(__out_ecount(pdwLen) LPSTR szParameters, __inout LPDWORD pdwLen)
    {
        ATLENSURE(szParameters);
        ATLENSURE(pdwLen);
        _ATLTRY
        {
            if(GetCount() == 0)
            {
                *szParameters = '\0';
                *pdwLen = 0;
                return TRUE;
            }

            CStringA strParams;
            POSITION pos = GetStartPosition();
            while(pos != NULL)
            {
                LPCSTR szBuf = GetKeyAt(pos);
                EscapeToCString(strParams, szBuf);
                szBuf = GetValueAt(pos);
                if(*szBuf)
                {
                    strParams += '=';
                    EscapeToCString(strParams, szBuf);
                }
                strParams += '&';
                GetNext(pos);
            }

            DWORD dwLen = strParams.GetLength();
            strParams.Delete(dwLen - 1);
            BOOL bRet = TRUE;
            if(dwLen > *pdwLen)
            {
                bRet = FALSE;
            }
            else
            {
                dwLen--;
                Checked::memcpy_s(szParameters, *pdwLen, static_cast<LPCSTR>(strParams), dwLen);
                szParameters[dwLen] = '\0';
            }

            *pdwLen = dwLen;
            return bRet;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }

}; // class CHttpRequestParams

#ifndef MAX_TOKEN_LENGTH
#define MAX_TOKEN_LENGTH (MAX_PATH)
#endif

// This class represents the information about a file that has been uploaded to the web server.
class CHttpRequestFile : public IHttpFile
{
private:

    // The name of the form field used to upload the file.
    CHAR m_szParamName[MAX_TOKEN_LENGTH];

    // The original file name of the uploaded file as set by the client.
    CHAR m_szFileName[MAX_PATH];

    // The original path and file name of the uploaded file as set by the client.
    CHAR m_szFullFileName[MAX_PATH];

    // The MIME type of the uploaded file.
    CHAR m_szContentType[MAX_TOKEN_LENGTH];

    // The name of the uploaded file on the server.
    CHAR m_szTempFileName[MAX_PATH];

    // The size of the file in bytes.
    ULONGLONG m_nFileSize;

public:

    CHttpRequestFile() throw()
    {
        *m_szParamName = '\0';
        *m_szFileName = '\0';
        *m_szFullFileName = '\0';
        *m_szContentType = '\0';
        m_nFileSize = 0;
    }

    __checkReturn BOOL Initialize(
        __in_opt LPCSTR pParamName,
        __in LPCSTR pFileName,
        __in_opt LPCSTR pTempFileName,
        __in_opt LPCSTR pContentType,
        __in const ULONGLONG &nFileSize)
    {
        ATLENSURE(pFileName != NULL);

        if(!SafeStringCopy(m_szFullFileName, pFileName))
        {
            // path too long
            return FALSE;
        }

        if(pParamName && *pParamName)
        {
            if(!SafeStringCopy(m_szParamName, pParamName))
            {
                // string too long
                return FALSE;
            }
        }

        if(pTempFileName && *pTempFileName)
        {
            if(!SafeStringCopy(m_szTempFileName, pTempFileName))
            {
                // path too long
                return FALSE;
            }
        }

        if(pContentType && *pContentType)
        {
            if(!SafeStringCopy(m_szContentType, pContentType))
            {
                // string too long
                return FALSE;
            }
        }

        // Set m_szFileName to be the file name without the path.
        // This is likely to be the most meaningful part of the
        // original file name once the file reaches the server.

        LPSTR szTmp = m_szFullFileName;
        LPSTR szFile = m_szFileName;
        LPSTR pszLastCharFileBuf = m_szFileName + _countof(m_szFileName) - 1;
        while(*szTmp)
        {
            if(*szTmp == '\\')
            {
                szFile = m_szFileName;
            }
            else
            {
                if(szFile > pszLastCharFileBuf)
                {
                    return FALSE;
                }
                *szFile++ = *szTmp;
            }
            szTmp++;
        }
        if(szFile > pszLastCharFileBuf)
        {
            return FALSE;
        }
        *szFile = 0;

        m_nFileSize = nFileSize;
        return TRUE;
    }


    //=======================================
    // IHttpFile interface
    //=======================================
    __checkReturn LPCSTR GetParamName()
    {
        return m_szParamName;
    }

    __checkReturn LPCSTR GetFileName()
    {
        return m_szFileName;
    }

    __checkReturn LPCSTR GetFullFileName()
    {
        return m_szFullFileName;
    }

    __checkReturn LPCSTR GetContentType()
    {
        return m_szContentType;
    }

    __checkReturn LPCSTR GetTempFileName()
    {
        return m_szTempFileName;
    }

    __checkReturn ULONGLONG GetFileSize()
    {
        return m_nFileSize;
    }

    void Free()
    {
        delete this;
    }

}; // class CHttpRequestFile


// utility function to ReadData from a ServerContext
ATL_NOINLINE inline
__checkReturn BOOL ReadClientData(__inout IHttpServerContext *pServerContext, __out_ecount_part(*pdwLen, *pdwLen) LPSTR pbDest, __inout LPDWORD pdwLen, __in DWORD dwBytesRead)
{
    ATLENSURE(pServerContext != NULL);
    ATLENSURE(pdwLen != NULL);
    ATLENSURE(pbDest != NULL);

    _ATLTRY
    {
        DWORD dwToRead = *pdwLen;
        DWORD dwAvailableBytes = pServerContext->GetAvailableBytes();
        DWORD dwRead(0);

        // Read from available data first
        if(dwBytesRead < dwAvailableBytes)
        {
            LPBYTE pbAvailableData = pServerContext->GetAvailableData();
            pbAvailableData += dwBytesRead;
            DWORD dwAvailableToRead = __min(dwToRead, dwAvailableBytes - dwBytesRead);
            Checked::memcpy_s(pbDest, *pdwLen, pbAvailableData, dwAvailableToRead);
            dwBytesRead += dwAvailableToRead;
            dwToRead -= dwAvailableToRead;
            pbDest += dwAvailableToRead;
            dwRead += dwAvailableToRead;
        }

        DWORD dwTotalBytes = pServerContext->GetTotalBytes();

        // If there is still more to read after the available data is exhausted
        if(dwToRead && dwBytesRead < dwTotalBytes)
        {
            DWORD dwClientBytesToRead = __min(pServerContext->GetTotalBytes() - dwBytesRead, dwToRead);
            DWORD dwClientBytesRead = 0;

            // keep on reading until we've read the amount requested
            do
            {
                dwClientBytesRead = dwClientBytesToRead;
                if(!pServerContext->ReadClient(pbDest, &dwClientBytesRead))
                {
                    return FALSE;
                }
                dwClientBytesToRead -= dwClientBytesRead;
                dwRead += dwClientBytesRead;
                pbDest += dwClientBytesRead;

            }
            while(dwClientBytesToRead != 0 && dwClientBytesRead != 0);


        }

        *pdwLen = dwRead;
    }
    _ATLCATCHALL()
    {
        return FALSE;
    }

    return TRUE;
}

#ifndef MAX_MIME_BOUNDARY_LEN
#define MAX_MIME_BOUNDARY_LEN 128
#endif

enum ATL_FORM_FLAGS
{
    ATL_FORM_FLAG_NONE = 0,
    ATL_FORM_FLAG_IGNORE_FILES = 1,
    ATL_FORM_FLAG_REFUSE_FILES = 2,
    ATL_FORM_FLAG_IGNORE_EMPTY_FILES = 4,
    ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS = 8,
};

// Use this class to read multipart/form-data from the associated server context
// and generate files as necessary from the data in the body of the request.
class CMultiPartFormParser
{
protected:

    LPSTR                       m_pCurrent;
    LPSTR                       m_pEnd;
    LPSTR                       m_pStart;
    CHAR                        m_szBoundary[MAX_MIME_BOUNDARY_LEN+2];
    DWORD                       m_dwBoundaryLen;
    BOOL                        m_bFinished;
    CComPtr<IHttpServerContext> m_spServerContext;

public:

    typedef CHttpMap<CStringA, IHttpFile*, CStringElementTraits<CStringA> > FILEMAPTYPE;

    CMultiPartFormParser(__in IHttpServerContext* pServerContext) throw() :
        m_pCurrent(NULL),
        m_pEnd(NULL),
        m_pStart(NULL),
        m_dwBoundaryLen(0),
        m_bFinished(FALSE),
        m_spServerContext(pServerContext)
    {
        *m_szBoundary = '\0';
    }

    ~CMultiPartFormParser() throw()
    {
        _ATLTRY
        {
            // free memory if necessary
            if(m_spServerContext->GetTotalBytes() > m_spServerContext->GetAvailableBytes())
            {
                free(m_pStart);
            }
        }
        _ATLCATCHALL()
        {
        }
    }

    // Call this function to read multipart/form-data from the current HTTP request,
    // allowing files to be uploaded to the web server.
    //
    // Returns TRUE on success, FALSE on failure.
    //
    // Forms can be sent to a web server using one of two encodings: application/x-www-form-urlencoded or multipart/form-data.
    // In addition to the simple name-value pairs typically associated with
    // application/x-www-form-urlencoded form data, multipart/form-data (as
    // described in RFC 2388) can also contain files to be uploaded
    // to the web server.
    //
    // This function will generate a physical file for each file contained in the multipart/form-data request body.
    // The generated files are stored in the server's temporary directory as returned by the
    // GetTempPath API and are named using the GetTempFileName API.
    // The information about each file can be obtained from the elements of the Files array.
    // You can retrieve the original name of the file on the client, the name of the generated file on the server,
    // the MIME content type of the uploaded file, the name of the form field associated with that file, and the size in
    // bytes of the file. All this information is exposed by the CHttpRequestFile objects in the array.
    //
    // In addition to generating files and populating the Files array with information about them,
    // this function also populates the pQueryParams array with the names and values of the other form fields
    // contained in the current request. The file fields are also added to this array. The value of these fields
    // is the full name of the generated file on the server.
    //
    //      Note that files can be generated even if this function returns FALSE unless you specify either the
    //      ATL_FORM_FLAG_IGNORE_FILES or the ATL_FORM_FLAG_REFUSE_FILES flag. If you don't specify one of these
    //      flags, you should always check the Files array for generated files and delete any that are no longer
    //      needed to prevent your web server from running out of disk space.
    //
    // dwFlags can be a combination of one or more of the following values:
    //      ATL_FORM_FLAG_NONE                  Default behavior.
    //      ATL_FORM_FLAG_IGNORE_FILES          Any attempt to upload files is ignored.
    //      ATL_FORM_FLAG_REFUSE_FILES          Any attempt to upload files is treated as a failure. The function will return FALSE.
    //      ATL_FORM_FLAG_IGNORE_EMPTY_FILES    Files with a size of zero bytes are ignored.
    //      ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS   Fields with no content are ignored.
    ATL_NOINLINE BOOL GetMultiPartData(
        __inout FILEMAPTYPE& Files,
        __inout CHttpRequestParams* pQueryParams,
        __in DWORD dwFlags = ATL_FORM_FLAG_NONE) throw()
    {
        _ATLTRY
        {
            if(!InitializeParser())
            {
                return FALSE;
            }

            //Get to the first boundary
            if(!ReadUntilBoundary())
            {
                return FALSE;
            }

            CStringA strParamName;
            CStringA strFileName;
            CStringA strContentType;
            CStringA strData;
            BOOL bFound;

            while(!m_bFinished)
            {
                // look for "name" field
                if(!GetMimeData(strParamName, "name=", sizeof("name=") - 1, &bFound, TRUE) || !bFound)
                {
                    ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                    return FALSE;
                }

                // see if it's a file
                if(!GetMimeData(strFileName, "filename=", sizeof("filename=") - 1, &bFound, TRUE))
                {
                    ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                    return FALSE;
                }

                if(bFound)
                {
                    if(dwFlags & ATL_FORM_FLAG_REFUSE_FILES)
                    {
                        return FALSE;
                    }

                    if(!strFileName.GetLength())
                    {
                        if(!ReadUntilBoundary())
                        {
                            return FALSE;
                        }
                        continue;
                    }

                    if(!GetMimeData(strContentType, "Content-Type:", sizeof("Content-Type:") - 1, &bFound, TRUE))
                    {
                        ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                        return FALSE;
                    }

                    // move to the actual uploaded data
                    if(!MoveToData())
                    {
                        ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                        return FALSE;
                    }

                    // if the user doesn't want files, don't save the file
                    if(dwFlags & ATL_FORM_FLAG_IGNORE_FILES)
                    {
                        if(!ReadUntilBoundary(NULL, NULL))
                        {
                            return FALSE;
                        }
                        continue;
                    }

                    CAtlTemporaryFile ctf;
                    HRESULT hr = ctf.Create();
                    if(hr != S_OK)
                        return FALSE;

                    if(!ReadUntilBoundary(NULL, &ctf))
                    {
                        ctf.Close();
                        return FALSE;
                    }
                    ULONGLONG nFileSize = 0;
                    if(ctf.GetSize(nFileSize) != S_OK)
                        return FALSE;

                    if((dwFlags & ATL_FORM_FLAG_IGNORE_EMPTY_FILES) && nFileSize == 0)
                    {
                        ctf.Close();
                        continue;
                    }

                    CAutoPtr<CHttpRequestFile> spFile;

                    CT2AEX < MAX_PATH + 1 > szTempFileNameA(ctf.TempFileName());

                    ATLTRY(spFile.Attach(new CHttpRequestFile()));
                    if(!spFile)
                    {
                        return FALSE;
                    }

                    if(!spFile->Initialize(strParamName, strFileName, szTempFileNameA, strContentType, nFileSize))
                    {
                        // one of the strings was too long
                        return FALSE;
                    }

                    if(!Files.SetAt(szTempFileNameA, spFile))
                    {
                        return FALSE;
                    }

                    spFile.Detach();

                    if(!pQueryParams || !pQueryParams->SetAt(strParamName, szTempFileNameA))
                    {
                        return FALSE;
                    }

                    ctf.HandsOff();

                    continue;
                }

                // move to the actual uploaded data
                if(!MoveToData())
                {
                    ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                    return FALSE;
                }

                if(!ReadUntilBoundary(&strData))
                {
                    return FALSE;
                }

                if((dwFlags & ATL_FORM_FLAG_IGNORE_EMPTY_FIELDS) && strData.GetLength() == 0)
                    continue;

                if(!pQueryParams || !pQueryParams->SetAt(strParamName, strData))
                {
                    return FALSE;
                }

            }

            return TRUE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }

private:

    // implementation

    // case insensitive substring search -- does not handle multibyte characters (unlike tolower)
    // allows searching up to a maximum point in a string
    inline char AtlCharLower(__in char ch) throw()
    {
        if(ch > 64 && ch < 91)
        {
            return ch + 32;
        }

        return ch;
    }

    inline __checkReturn char * _stristr(__in const char * str1, __in const char * str2)
    {
        ATLENSURE(str1 != NULL);
        ATLENSURE(str2 != NULL);
        char *cp = (char *) str1;
        char *s1, *s2;

        if(!*str2)
            return((char *)str1);

        while(*cp)
        {
            s1 = cp;
            s2 = (char *) str2;

            while(*s1 && *s2 && !(AtlCharLower(*s1) - AtlCharLower(*s2)))
            {
                s1++, s2++;
            }

            if(!*s2)
            {
                return(cp);
            }

            cp++;
        }

        return(NULL);
    }


    inline __checkReturn char * _stristrex(__in const char * str1, __in const char * str2, __in const char * str1End)
    {
        ATLENSURE(str1 != NULL);
        ATLENSURE(str2 != NULL);
        char *cp = (char *) str1;
        char *s1, *s2;

        if(!*str2)
            return((char *)str1);

        while(cp != str1End)
        {
            s1 = cp;
            s2 = (char *) str2;

            while(s1 != str1End && *s2 && !(AtlCharLower(*s1) - AtlCharLower(*s2)))
            {
                s1++, s2++;
            }

            if(!*s2)
            {
                return (cp);
            }

            if(s1 == str1End)
            {
                return (NULL);
            }

            cp++;
        }

        return(NULL);
    }

    inline __checkReturn char * _strstrex(__in const char * str1, __in const char * str2, __in const char * str1End)
    {
        ATLENSURE(str1 != NULL);
        ATLENSURE(str2 != NULL);
        char *cp = (char *) str1;
        char *s1, *s2;

        if(!*str2)
            return((char *)str1);

        while(cp != str1End)
        {
            s1 = cp;
            s2 = (char *) str2;

            while(s1 != str1End && *s2 && !((*s1) - (*s2)))
            {
                s1++, s2++;
            }

            if(!*s2)
            {
                return (cp);
            }

            if(s1 == str1End)
            {
                return (NULL);
            }

            cp++;
        }

        return(NULL);
    }

    ATL_NOINLINE __checkReturn BOOL InitializeParser() throw()
    {
        ATLASSUME(m_spServerContext != NULL);

        _ATLTRY
        {
            DWORD dwBytesTotal = m_spServerContext->GetTotalBytes();

            // if greater than bytes available, allocate necessary space
            if(dwBytesTotal > m_spServerContext->GetAvailableBytes())
            {
                m_pStart = (LPSTR) malloc(dwBytesTotal);
                if(!m_pStart)
                {
                    return FALSE;
                }
                m_pCurrent = m_pStart;
                DWORD dwLen = dwBytesTotal;
                if(!ReadClientData(m_spServerContext, m_pStart, &dwLen, 0) || dwLen != dwBytesTotal)
                {
                    return FALSE;
                }
            }
            else
            {
                m_pStart = (LPSTR) m_spServerContext->GetAvailableData();
            }

            m_pCurrent = m_pStart;
            m_pEnd = m_pCurrent + dwBytesTotal;

            //get the boundary
            LPCSTR pszContentType = m_spServerContext->GetContentType();
            ATLASSERT(pszContentType != NULL);

            LPCSTR pszTmp = _stristr(pszContentType, "boundary=");
            if(!pszTmp)
            {
                ATLTRACE(atlTraceISAPI, 0, _T("Malformed Form-Data"));
                return FALSE;
            }

            pszTmp += sizeof("boundary=") - 1;
            BOOL bInQuote = FALSE;
            if(*pszTmp == '\"')
            {
                bInQuote = TRUE;
                pszTmp++;
            }

            LPSTR pszMimeBoundary = m_szBoundary;
            *pszMimeBoundary++ = '-';
            *pszMimeBoundary++ = '-';
            m_dwBoundaryLen = 2;
            while(*pszTmp && (bInQuote || IsStandardBoundaryChar(*pszTmp)))
            {
                if(m_dwBoundaryLen >= MAX_MIME_BOUNDARY_LEN)
                {
                    ATLTRACE(atlTraceISAPI, 0, _T("Malformed MIME boundary"));
                    return FALSE;
                }

                if(*pszTmp == '\r' || *pszTmp == '\n')
                {
                    if(bInQuote)
                    {
                        pszTmp++;
                        continue;
                    }
                    break;
                }
                if(bInQuote && *pszTmp == '"')
                {
                    break;
                }

                *pszMimeBoundary++ = *pszTmp++;
                m_dwBoundaryLen++;
            }

            *pszMimeBoundary = '\0';
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }

        return TRUE;
    }

    inline __checkReturn BOOL MoveToData() throw()
    {
        LPSTR szEnd = _strstrex(m_pCurrent, "\r\n\r\n", m_pEnd);
        if(!szEnd)
        {
            return FALSE;
        }

        m_pCurrent = szEnd + 4;
        if(m_pCurrent >= m_pEnd)
        {
            return FALSE;
        }

        return TRUE;
    }

    inline __checkReturn BOOL GetMimeData(__inout CStringA &str, __in_ecount(dwFieldLen) LPCSTR szField, __in DWORD dwFieldLen, __out LPBOOL pbFound, __in BOOL bIgnoreCase = FALSE) throw()
    {
        _ATLTRY
        {
            ATLASSERT(szField != NULL);
            ATLENSURE(pbFound != NULL);

            *pbFound = FALSE;

            LPSTR szEnd = _strstrex(m_pCurrent, "\r\n\r\n", m_pEnd);
            if(!szEnd)
            {
                return FALSE;
            }

            LPSTR szDataStart = NULL;

            if(!bIgnoreCase)
            {
                szDataStart = _strstrex(m_pCurrent, szField, szEnd);
            }
            else
            {
                szDataStart = _stristrex(m_pCurrent, szField, szEnd);
            }

            if(szDataStart)
            {
                szDataStart += dwFieldLen;
                if(szDataStart >= m_pEnd)
                {
                    return FALSE;
                }

                BOOL bInQuote = FALSE;
                if(*szDataStart == '\"')
                {
                    bInQuote = TRUE;
                    szDataStart++;
                }

                LPSTR szDataEnd = szDataStart;
                while(!bInQuote && (szDataEnd < m_pEnd) && (*szDataEnd == ' ' || *szDataEnd == '\t'))
                {
                    szDataEnd++;
                }

                if(szDataEnd >= m_pEnd)
                {
                    return FALSE;
                }

                while(szDataEnd < m_pEnd)
                {
                    if(!IsValidTokenChar(*szDataEnd))
                    {
                        if(*szDataEnd == '\"' || !bInQuote)
                        {
                            break;
                        }
                    }
                    szDataEnd++;
                }

                if(szDataEnd >= m_pEnd)
                {
                    return FALSE;
                }

                str.SetString(szDataStart, (int)(szDataEnd - szDataStart));
                *pbFound = TRUE;
            }

            return TRUE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }

    ATL_NOINLINE __checkReturn BOOL ReadUntilBoundary(__inout_opt CStringA* pStrData = NULL, __inout_opt CAtlTemporaryFile* pCtf = NULL) throw()
    {
        _ATLTRY
        {
            LPSTR szBoundaryStart = m_pCurrent;
            LPSTR szBoundaryEnd = NULL;

            do
            {
                szBoundaryStart = _strstrex(szBoundaryStart, m_szBoundary, m_pEnd);
                if(szBoundaryStart)
                {
                    if((szBoundaryStart - m_pStart) >= 2)
                    {
                        if(*(szBoundaryStart - 1) != 0x0a || *(szBoundaryStart - 2) != 0x0d)
                        {
                            szBoundaryStart++;
                            continue;
                        }
                    }
                    szBoundaryEnd = szBoundaryStart + m_dwBoundaryLen;
                    if(szBoundaryEnd + 2 >= m_pEnd)
                    {
                        return FALSE;
                    }
                    if(szBoundaryEnd[0] == '\r' && szBoundaryEnd[1] == '\n')
                    {
                        break;
                    }
                    if(szBoundaryEnd[0] == '-' && szBoundaryEnd[1] == '-')
                    {
                        m_bFinished = TRUE;
                        break;
                    }
                    szBoundaryStart++;
                }
            }
            while(szBoundaryStart && szBoundaryStart < m_pEnd);

            if(!szBoundaryStart || szBoundaryStart >= m_pEnd)
            {
                return FALSE;
            }

            if((szBoundaryStart - m_pStart) >= 2)
            {
                szBoundaryStart -= 2;
            }
            if(pStrData)
            {
                pStrData->SetString(m_pCurrent, (int)(szBoundaryStart - m_pCurrent));
            }
            if(pCtf)
            {
                if(FAILED(pCtf->Write(m_pCurrent, (DWORD)(szBoundaryStart - m_pCurrent))))
                {
                    return FALSE;
                }
            }

            if(!m_bFinished)
            {
                m_pCurrent = szBoundaryEnd + 2;
                if(m_pCurrent >= m_pEnd)
                {
                    return FALSE;
                }
            }

            return TRUE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }

    static inline __checkReturn BOOL IsStandardBoundaryChar(__in CHAR ch) throw()
    {
        if((ch >= 'A' && ch <= 'Z') ||
           (ch >= 'a' && ch <= 'z') ||
           (ch >= '0' && ch <= '9') ||
           (ch == '\'') ||
           (ch == '+')  ||
           (ch == '_')  ||
           (ch == '-')  ||
           (ch == '=')  ||
           (ch == '?'))
        {
            return TRUE;
        }

        return FALSE;
    }

    inline __checkReturn BOOL IsValidTokenChar(__in CHAR ch) throw()
    {
        return ((ch != 0) && (ch != 0xd) && (ch != 0xa) && (ch != ' ') && (ch != '\"'));
    }

private:
    // Prevents copying.
    CMultiPartFormParser(__in const CMultiPartFormParser& /*that*/) throw()
    {
        ATLASSERT(FALSE);
    }

    const CMultiPartFormParser& operator=(__in const CMultiPartFormParser& /*that*/) throw()
    {
        ATLASSERT(FALSE);
        return (*this);
    }
}; // class CMultiPartFormParser


// 48K max form size
#ifndef DEFAULT_MAX_FORM_SIZE
#define DEFAULT_MAX_FORM_SIZE 49152
#endif

// This class provides access to the information contained in an HTTP request submitted to a web server.
//
// CHttpRequest provides access to the query string parameters, form fields, cookies, and files
// that make up an HTTP request, as well as many other important properties of the request.
class CHttpRequest : public IHttpRequestLookup
{
protected:
    // Implementation: Array used to map an HTTP request method (for example, "GET" or "POST")
    // from a string to a numeric constant from the HTTP_METHOD enum (HTTP_METHOD_GET or HTTP_METHOD_HEAD).
    static const char* const m_szMethodStrings[];

    // Implementation: The server context.
    CComPtr<IHttpServerContext> m_spServerContext;

    // Implementation: The number of bytes read from the body of the request.
    DWORD m_dwBytesRead;

    // Implementation: TRUE if the request method was POST and the encoding was
    // multipart/form-data, FALSE otherwise.
    BOOL m_bMultiPart;

    CCookie m_EmptyCookie;

    // Implementation: Constructor function used to reinitialize all data members.
    void Construct() throw()
    {
        m_spServerContext.Release();
        m_bMultiPart = FALSE;
        m_dwBytesRead = 0;
        if(m_pFormVars != &m_QueryParams)
            delete m_pFormVars;

        m_pFormVars = NULL;
        m_pFormVars = &m_QueryParams;
        m_QueryParams.RemoveAll();
        m_QueryParams.SetShared(false);

        ClearFilesAndCookies();
    }

    void ClearFilesAndCookies() throw()
    {
        m_Files.RemoveAll();
        m_Files.SetShared(false);
        m_requestCookies.RemoveAll();
        m_requestCookies.SetShared(false);
    }

public:

    // Implementation: The collection of query parameters (name-value pairs) obtained from the query string.
    CHttpRequestParams m_QueryParams;

    // Implementation: The collection of form fields (name-value pairs).
    // The elements of this collection are obtained from the query string for a GET request,
    // or from the body of the request for a POST request.
    CHttpRequestParams *m_pFormVars;

    // The array of CHttpRequestFiles obtained from the current request.
    // See CHttpRequest::Initialize and CMultiPartFormParser::GetMultiPartData for more information.
    typedef CHttpPtrMap<CStringA, IHttpFile*, CStringElementTraits<CStringA> > FileMap;
    FileMap m_Files;

    // Implementation: The array of cookies obtained from the current request.
    typedef CHttpMap<CStringA, CCookie, CStringElementTraits<CStringA> > CookieMap;
    CookieMap m_requestCookies;

    // Numeric constants for the HTTP request methods (such as GET and POST).
    enum HTTP_METHOD
    {
        HTTP_METHOD_UNKNOWN = -1,
        HTTP_METHOD_GET,
        HTTP_METHOD_POST,
        HTTP_METHOD_HEAD,
        HTTP_METHOD_DELETE,
        HTTP_METHOD_LINK,
        HTTP_METHOD_UNLINK,
        HTTP_METHOD_DEBUG  // Debugging support for VS7
    };

    // The collection of query parameters (name-value pairs) obtained from the query string.
    // A read-only property.
    __declspec(property(get = GetQueryParams)) const CHttpRequestParams& QueryParams;

    // Returns a reference to the collection of query parameters(name-value pairs)
    // obtained from the query string.
    const CHttpRequestParams& GetQueryParams() const throw()
    {
        return m_QueryParams;
    }

    // The collection of form fields (name-value pairs).
    // The elements of this collection are obtained from the query string for a GET request,
    // or from the body of the request for a POST request.
    // A read-only property.
    __declspec(property(get = GetFormVars)) const CHttpRequestParams& FormVars;

    // Returns a reference to the collection of form fields (name-value pairs)
    // obtained from the query string for a GET request,
    // or from the body of the request for a POST request.
    const CHttpRequestParams& GetFormVars() const throw()
    {
        return *m_pFormVars;
    }

    CHttpRequest() throw()
    {
        m_bMultiPart = FALSE;
        m_dwBytesRead = 0;
        m_pFormVars = &m_QueryParams;
    }

    virtual ~CHttpRequest() throw()
    {
        DeleteFiles();

        if(m_pFormVars != &m_QueryParams)
        {
            delete m_pFormVars;
            m_pFormVars = NULL;
        }
    }

    // Constructs and initializes the object.
    CHttpRequest(
        __in IHttpServerContext *pServerContext,
        __in DWORD dwMaxFormSize = DEFAULT_MAX_FORM_SIZE,
        __in DWORD dwFlags = ATL_FORM_FLAG_NONE) throw(...)
        : m_pFormVars(NULL)
    {
        Construct();
        if(!Initialize(pServerContext, dwMaxFormSize, dwFlags))
            AtlThrow(E_FAIL);
    }

    CHttpRequest(__in IHttpRequestLookup *pRequestLookup) throw(...)
        : m_pFormVars(NULL)
    {
        if(!Initialize(pRequestLookup))  // Calls Construct for you
            AtlThrow(E_FAIL);
    }

    //=========================================================================================
    // BEGIN IHttpRequestLoookup interface
    //=========================================================================================
    __success(return != NULL) __checkReturn POSITION GetFirstQueryParam(__deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
    {
        POSITION pos = m_QueryParams.GetStartPosition();
        if(pos != NULL)
        {
            ATLENSURE(ppszName != NULL);
            ATLENSURE(ppszValue != NULL);
            *ppszName = m_QueryParams.GetKeyAt(pos);
            *ppszValue = m_QueryParams.GetValueAt(pos);
        }

        return pos;
    }

    __success(return != NULL) __checkReturn POSITION GetNextQueryParam(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
    {
        ATLENSURE(pos != NULL);


        POSITION posNext(pos);
        m_QueryParams.GetNext(posNext);
        if(posNext != NULL)
        {
            ATLENSURE(ppszName != NULL);
            ATLENSURE(ppszValue != NULL);
            *ppszName = m_QueryParams.GetKeyAt(posNext);
            *ppszValue = m_QueryParams.GetValueAt(posNext);
        }

        return posNext;
    }

    __success(return != NULL) __checkReturn POSITION GetFirstFormVar(__deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
    {
        // if no form vars and just pointing to the query params,
        // then return NULL
        if(m_pFormVars == &m_QueryParams)
            return NULL;

        POSITION pos = m_pFormVars->GetStartPosition();
        if(pos != NULL)
        {
            ATLENSURE(ppszName != NULL);
            ATLENSURE(ppszValue != NULL);
            *ppszName = m_pFormVars->GetKeyAt(pos);
            *ppszValue = m_pFormVars->GetValueAt(pos);
        }

        return pos;
    }

    __success(return != NULL) __checkReturn POSITION GetNextFormVar(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out LPCSTR *ppszValue)
    {
        ATLASSERT(pos != NULL);
        ATLASSERT(ppszName != NULL);
        ATLASSERT(ppszValue != NULL);

        POSITION posNext(pos);
        m_pFormVars->GetNext(posNext);
        if(posNext != NULL)
        {
            *ppszName = m_pFormVars->GetKeyAt(posNext);
            *ppszValue = m_pFormVars->GetValueAt(posNext);
        }

        return posNext;
    }

    POSITION GetFirstFile(__in LPCSTR *ppszName, __deref_out IHttpFile **ppFile)
    {
        ATLASSERT(ppszName != NULL);
        ATLASSERT(ppFile != NULL);

        POSITION pos = m_Files.GetStartPosition();
        if(pos != NULL)
        {
            *ppszName = m_Files.GetKeyAt(pos);
            *ppFile = m_Files.GetValueAt(pos);
        }

        return pos;
    }

    __success(return != NULL) __checkReturn POSITION GetNextFile(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out IHttpFile **ppFile)
    {
        ATLASSERT(pos != NULL);
        ATLASSERT(ppszName != NULL);
        ATLASSERT(ppFile != NULL);

        m_Files.GetNext(pos);
        if(pos != NULL)
        {
            *ppszName = m_Files.GetKeyAt(pos);
            *ppFile = m_Files.GetValueAt(pos);
        }

        return pos;
    }

    // Returns a pointer to the IHttpServerContext interface for the current request.
    HRESULT GetServerContext(__deref_out_opt IHttpServerContext ** ppOut)
    {
        return m_spServerContext.CopyTo(ppOut);
    }
    //=========================================================================================
    // END IHttpRequestLookup interface
    //=========================================================================================

    __success(return != NULL) __checkReturn POSITION GetFirstCookie(__deref_out LPCSTR *ppszName, __deref_out const CCookie **ppCookie) throw()
    {
        ATLASSERT(ppszName != NULL);
        ATLASSERT(ppCookie != NULL);
        POSITION pos = NULL;
        if(GetRequestCookies())
        {
            pos = m_requestCookies.GetStartPosition();
            if(pos != NULL)
            {
                *ppszName = m_requestCookies.GetKeyAt(pos);
                *ppCookie = &(m_requestCookies.GetValueAt(pos));
            }
        }
        return pos;
    }

    __success(return != NULL) __checkReturn POSITION GetNextCookie(__in POSITION pos, __deref_out LPCSTR *ppszName, __deref_out const CCookie **ppCookie) throw()
    {
        ATLASSERT(pos != NULL);
        ATLASSERT(ppszName != NULL);
        ATLASSERT(ppCookie != NULL);

        POSITION posNext(pos);
        m_requestCookies.GetNext(posNext);
        if(posNext != NULL)
        {
            *ppszName = m_requestCookies.GetKeyAt(posNext);
            *ppCookie = &(m_requestCookies.GetValueAt(posNext));
        }
        return posNext;
    }

    void SetServerContext(__in IHttpServerContext *pServerContext) throw()
    {
        m_spServerContext = pServerContext;
    }

    BOOL Initialize(__in IHttpRequestLookup *pRequestLookup) throw()
    {
        _ATLTRY
        {
            ATLASSERT(pRequestLookup != NULL);
            // if there's no pRequestLookup, just return
            if(!pRequestLookup)
                return TRUE;

            Construct();
            HRESULT hr = pRequestLookup->GetServerContext(&m_spServerContext);
            if(FAILED(hr))
                return FALSE;

            LPCSTR szName(NULL);
            LPCSTR szValue(NULL);

            // Initialize query params from the IHttpRequestLookup*
            POSITION pos(pRequestLookup->GetFirstQueryParam(&szName, &szValue));
            while(pos != NULL)
            {
                m_QueryParams.SetAt(szName, szValue);
                pos = pRequestLookup->GetNextQueryParam(pos, &szName, &szValue);
            }
            m_QueryParams.SetShared(true);

            // Initialize the form vars from the IHttpRequestLookup*
            pos = pRequestLookup->GetFirstFormVar(&szName, &szValue);
            if(pos)
            {
                m_pFormVars = NULL;
                ATLTRY(m_pFormVars = new CHttpRequestParams);
                if(!m_pFormVars)
                    return FALSE;

                while(pos != NULL)
                {
                    m_pFormVars->SetAt(szName, szValue);
                    pos = pRequestLookup->GetNextFormVar(pos, &szName, &szValue);
                }
                m_pFormVars->SetShared(true);
            }
            else
            {
                m_pFormVars = &m_QueryParams;
            }

            // Initialize the files from the IHttpRequestLookup*
            IHttpFile *pFile(NULL);
            pos = pRequestLookup->GetFirstFile(&szName, &pFile);
            while(pos != NULL)
            {
                m_Files.SetAt(szName, pFile);
                pos = pRequestLookup->GetNextFile(pos, &szName, &pFile);
            }
            m_Files.SetShared(true);

            // Initialzie the cookies form the IHttpRequestLookup*
            BOOL bRet = FALSE;
            CStringA strCookies;
            bRet = GetCookies(strCookies);
            if(bRet)
            {
                bRet = Parse(strCookies);
            }
            m_requestCookies.SetShared(false);
            return bRet;
        } // _ATLTRY
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to initialize the object with information about the current request.
    //
    // Returns TRUE on success, FALSE on failure.
    //
    // Call Initialize directly or via the appropriate constructor before using the methods and
    // properties of the request object.
    //
    // Initialize does the following:
    //
    //      Parses and decodes the query string into a collection of name-value pairs.
    //      This collection is accessible via the GetQueryParams method or the QueryParams property.
    //
    //      Sets m_bMultiPart to TRUE if the request is a POST request with multipart/form-data encoding.
    //
    //      Parses the body of a POST request if the size of the request data is less than or equal to dwMaxFormSize.
    //      The body of the request will consist of simple form fields and may also contain files if the request is encoded as multipart/form-data.
    //      In that case, the dwFlags parameter is passed to CMultiPartFormParser::GetMultiPartData to control the creation of the files.
    //      The collection of form fields is accessible via the GetFormVars method or the FormVars property.
    //      The collection of files is accessible via the m_Files member.
    //
    // Note that Initialize does not parse the cookies associated with a request.
    // Cookies are not processed until an attempt is made to access a cookie in the collection.
    BOOL Initialize(
        __in IHttpServerContext *pServerContext,
        __in DWORD dwMaxFormSize = DEFAULT_MAX_FORM_SIZE,
        __in DWORD dwFlags = ATL_FORM_FLAG_NONE) throw()
    {
        _ATLTRY
        {
            ATLASSERT(pServerContext != NULL);
            if(!pServerContext)
            {
                return FALSE;
            }

            m_spServerContext = pServerContext;

            HTTP_METHOD httpMethod = GetMethod();
            LPCSTR pszQueryString = GetQueryString();
            if(pszQueryString && *pszQueryString)
            {
                // Parse the query string.
                CHAR szQueryString[ATL_URL_MAX_URL_LENGTH];
                if(!SafeStringCopy(szQueryString, pszQueryString))
                {
                    return FALSE;
                }

                if(!m_QueryParams.Parse(szQueryString))
                {
                    return FALSE;
                }
            }

            if(m_QueryParams.IsShared())
            {
                return TRUE;
            }

            // If this is a GET request, the collection of form fields
            // is the same as the collection of query parameters.
            if(httpMethod == HTTP_METHOD_GET)
            {
                m_pFormVars = &m_QueryParams;
                return TRUE;
            }
            else if(httpMethod == HTTP_METHOD_POST)
            {
                LPCSTR szContentType = GetContentType();
                if(!szContentType)
                    return FALSE;

                // Don't parse the form data if the size is bigger than the maximum specified.
                if(m_spServerContext->GetTotalBytes() > dwMaxFormSize)
                {
                    if(strncmp(szContentType, "multipart/form-data", 19) == 0)
                        m_bMultiPart = TRUE;

                    m_dwBytesRead = 0;

                    return TRUE;
                }

                // If POSTed data is urlencoded, call InitFromPost.
                if(strncmp(szContentType, "application/x-www-form-urlencoded", 33) == 0 && !m_pFormVars->IsShared())
                    return InitFromPost();

                // If POSTed data is encoded as multipart/form-data, use CMultiPartFormParser.
                if(strncmp(szContentType, "multipart/form-data", 19) == 0 && !m_pFormVars->IsShared())
                {
                    if(m_pFormVars != &m_QueryParams)
                        delete m_pFormVars;
                    m_pFormVars = NULL;

                    CMultiPartFormParser FormParser(m_spServerContext);
                    ATLTRY(m_pFormVars = new CHttpRequestParams);
                    if(!m_pFormVars)
                        return FALSE;

                    BOOL bRet = FormParser.GetMultiPartData(m_Files, m_pFormVars, dwFlags);
                    return bRet;
                }

                // else initialize m_dwBytesRead for ReadData
                m_dwBytesRead = 0;
            }

            return TRUE;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;

    }

    // Implementation: Call this function to initialize the collection of form fields
    // from the body of an application/x-www-form-urlencoded POST request.
    ATL_NOINLINE BOOL InitFromPost() throw()
    {
        _ATLTRY
        {
            ATLASSUME(m_spServerContext != NULL);

            // create our m_pFormVars
            if(m_pFormVars == NULL || m_pFormVars == &m_QueryParams)
            {
                ATLTRY(m_pFormVars = new CHttpRequestParams);
                if(m_pFormVars == NULL)
                {
                    return FALSE;
                }
            }

            // read the form data into a buffer
            DWORD dwBytesTotal = m_spServerContext->GetTotalBytes();
            CAutoVectorPtr<CHAR> szBuff;
            if((dwBytesTotal + 1 < dwBytesTotal) || (dwBytesTotal < 0))
            {
                return FALSE;
            }
            if(!szBuff.Allocate(dwBytesTotal + 1))
            {
                return FALSE;
            }
            // first copy the available
            BOOL bRet = ReadClientData(m_spServerContext, szBuff, &dwBytesTotal, 0);
            if(bRet)
            {
                szBuff[dwBytesTotal] = '\0';
                bRet = m_pFormVars->Parse(szBuff);
            }

            return bRet;
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to remove the files listed in m_Files from the web server's hard disk.
    // Returns the number of files deleted.
    int DeleteFiles() throw()
    {
        int nDeleted = 0;
        POSITION pos = m_Files.GetStartPosition();
        while(pos != NULL)
        {
            LPCSTR szTempFile = m_Files.GetKeyAt(pos);
            if(szTempFile && DeleteFileA(szTempFile))
            {
                nDeleted++;
            }
            m_Files.GetNext(pos);
        }

        return nDeleted;
    }

    // Read a specified amount of data into pbDest and return the bytes read in pdwLen.
    // Returns TRUE on success, FALSE on failure.
    BOOL ReadData(__out_ecount_part(*pdwLen, *pdwLen) LPSTR pDest, __inout LPDWORD pdwLen)
    {
        ATLENSURE(pDest);
        ATLENSURE(pdwLen);

        BOOL bRet = ReadClientData(m_spServerContext, pDest, pdwLen, m_dwBytesRead);
        if(bRet)
            m_dwBytesRead += *pdwLen;
        return bRet;
    }

    // Returns the number of bytes available in the request buffer accessible via GetAvailableData.
    // If GetAvailableBytes returns the same value as GetTotalBytes, the request buffer contains the whole request.
    // Otherwise, the remaining data should be read from the client using ReadData.
    // Equivalent to EXTENSION_CONTROL_BLOCK::cbAvailable.
    __checkReturn DWORD GetAvailableBytes() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetAvailableBytes();
    }

    // Returns the total number of bytes to be received from the client.
    // If this value is 0xffffffff, then there are four gigabytes or more of available data.
    // In this case, ReadData should be called until no more data is returned.
    // Equivalent to the CONTENT_LENGTH server variable or EXTENSION_CONTROL_BLOCK::cbTotalBytes.
    __checkReturn DWORD GetTotalBytes() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetTotalBytes();
    }

    // Returns a pointer to the request buffer containing the data sent by the client.
    // The size of the buffer can be determined by calling GetAvailableBytes.
    // Equivalent to EXTENSION_CONTROL_BLOCK::lpbData
    __checkReturn LPBYTE GetAvailableData() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetAvailableData();
    }


    // Returns a nul-terminated string that contains the query information.
    // This is the part of the URL that appears after the question mark (?).
    // Equivalent to the QUERY_STRING server variable or EXTENSION_CONTROL_BLOCK::lpszQueryString.
    __checkReturn LPCSTR GetQueryString() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetQueryString();
    }

    // Returns a nul-terminated string that contains the HTTP method of the current request.
    // Examples of common HTTP methods include "GET" and "POST".
    // Equivalent to the REQUEST_METHOD server variable or EXTENSION_CONTROL_BLOCK::lpszMethod.
    __checkReturn LPCSTR GetMethodString() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetRequestMethod();
    }

    // Returns an HTTP_METHOD enum value corresponding to the HTTP method of the current request.
    // Returns HTTP_METHOD_UNKNOWN if the request method is not one of the following methods:
    //      GET
    //      POST
    //      HEAD
    //      DELETE
    //      LINK
    //      UNLINK
    __checkReturn HTTP_METHOD GetMethod() throw(...)
    {
        LPCSTR szMethod = GetMethodString();
        if(!szMethod)
            return HTTP_METHOD_UNKNOWN;
        for(int i = 0; m_szMethodStrings[i]; i++)
        {
            if(strcmp(szMethod, m_szMethodStrings[i]) == 0)
                return (HTTP_METHOD) i;
        }
        return HTTP_METHOD_UNKNOWN;
    }

    // Returns a nul-terminated string that contains the content type of the data sent by the client.
    // Equivalent to the CONTENT_TYPE server variable or EXTENSION_CONTROL_BLOCK::lpszContentType.
    __checkReturn LPCSTR GetContentType() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetContentType();
    }


    // Call this function to retrieve a nul-terminated string containing the value of the "AUTH_USER" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetAuthUserName(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("AUTH_USER", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "APPL_PHYSICAL_PATH" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetPhysicalPath(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("APPL_PHYSICAL_PATH", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "AUTH_PASSWORD" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetAuthUserPassword(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("AUTH_PASSWORD", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "URL" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUrl(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("URL", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_HOST" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUserHostName(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("REMOTE_HOST", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_ADDR" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUserHostAddress(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("REMOTE_ADDR", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the physical path of the script.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    // The script path is the same as GetPathTranslated up to the first .srf or .dll.
    // For example, if GetPathTranslated returns "c:\inetpub\vcisapi\hello.srf\goodmorning",
    // then this function returns "c:\inetpub\vcisapi\hello.srf".
    __checkReturn LPCSTR GetScriptPathTranslated() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetScriptPathTranslated();
    }

    // Returns a nul-terminated string that contains the physical path of the requested resource on the local server.
    // Equivalent to the PATH_TRANSLATED server variable or EXTENSION_CONTROL_BLOCK::lpszPathTranslated.
    __checkReturn LPCSTR GetPathTranslated() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetPathTranslated();
    }

    // Returns a nul-terminated string that contains the path of the current request.
    // This is the part of the URL that appears after the server name, but before the query string.
    // Equivalent to the PATH_INFO server variable or EXTENSION_CONTROL_BLOCK::lpszPathInfo.
    __checkReturn LPCSTR GetPathInfo() throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetPathInfo();
    }

    // Call this function to determine whether the current request was authenticated.
    __checkReturn BOOL GetAuthenticated() throw(...)
    {
        // We assume that if we get an authentication type from IIS,
        // then the user has been authenticated.
        CStringA strAuthType;
        if(GetAuthenticationType(strAuthType) &&
           strAuthType.GetLength() > 0)
            return TRUE;

        return FALSE;
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "AUTH_TYPE" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetAuthenticationType(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("AUTH_TYPE", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "REMOTE_USER" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUserName(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, DWORD __inout *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("REMOTE_USER", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "HTTP_USER_AGENT" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUserAgent(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("HTTP_USER_AGENT", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT_LANGUAGE" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUserLanguages(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("HTTP_ACCEPT_LANGUAGE", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetAcceptTypes(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("HTTP_ACCEPT", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "HTTP_ACCEPT_ENCODING" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetAcceptEncodings(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("HTTP_ACCEPT_ENCODING", szBuff, pdwSize);
    }


    // Call this function to retrieve a nul-terminated string containing the value of the "HTTP_REFERER" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetUrlReferer(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("HTTP_REFERER", szBuff, pdwSize);
    }

    // Call this function to retrieve a nul-terminated string containing the value of the "SCRIPT_NAME" server variable.
    //
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    //
    // On entry, pdwSize should point to a DWORD that indicates the size of the buffer in bytes.
    // On exit, the DWORD contains the number of bytes transferred or available to be transferred into the buffer (including the nul-terminating byte).
    __checkReturn BOOL GetScriptName(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuff, __inout DWORD *pdwSize) throw(...)
    {
        ATLENSURE(m_spServerContext);
        return m_spServerContext->GetServerVariable("SCRIPT_NAME", szBuff, pdwSize);
    }

    // Fills a buffer with the contents of the HTTP_COOKIE headers sent
    // from the browser.
    __checkReturn BOOL GetCookies(__out_ecount_part(*pdwSize, *pdwSize) LPSTR szBuf, __inout LPDWORD pdwSize) const throw(...)
    {
        ATLASSERT(szBuf != NULL);

        CStringA strCookie;
        if(GetCookies(strCookie))
        {
            ATLENSURE(pdwSize != NULL);
            if(pdwSize && *pdwSize > (DWORD)strCookie.GetLength())
            {
                Checked::strcpy_s(szBuf, *pdwSize, strCookie);
                *pdwSize = strCookie.GetLength();
                return true;
            }
        }
        return false;
    }

    // Fills a CStringA with the contents of the HTTP_COOKIE headers sent
    // from the browser.
    __checkReturn BOOL GetCookies(__inout CStringA& strBuff) const throw()
    {
        return GetServerVariable("HTTP_COOKIE", strBuff);
    }

    // Call this function to retrieve a reference to the specified cookie.
    // Returns a CCookie reference to the specified cookie or a
    // reference to an empty cookie if the name can not be found.
    ATL_NOINLINE const CCookie& Cookies(__in LPCSTR szName) throw(...)
    {
        if(GetRequestCookies())
        {
            // p->m_value is a const CCookie&
            CookieMap::CPair *p = NULL;
            ATLTRY(p = m_requestCookies.Lookup(szName));
            if(p)
            {
                return p->m_value;
            }
        }
        m_EmptyCookie.Empty(); // make sure it is cleared.
        return m_EmptyCookie;
    }


    // Call this function to retrieve the session cookie.
    const CCookie& GetSessionCookie() throw(...)
    {
        return Cookies(SESSION_COOKIE_NAME);
    }

    // Call this function to retrieve the value of the requested server variable in a CStringA object.
    // Returns TRUE on success, and FALSE on failure. Call GetLastError to get extended error information.
    // Equivalent to EXTENSION_CONTROL_BLOCK::GetServerVariable.
    BOOL GetServerVariable(__in LPCSTR szVariable, __out CStringA &str) const
    {
        ATLENSURE(m_spServerContext);

        DWORD dwSize = 0;
        BOOL bRet = FALSE;
        _ATLTRY
        {
            m_spServerContext->GetServerVariable(szVariable, NULL, &dwSize);
            bRet = m_spServerContext->GetServerVariable(szVariable, str.GetBuffer(dwSize), &dwSize);
            if(dwSize > 0)
                dwSize--;
            str.ReleaseBuffer(dwSize);
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;
    }

    // Call this function to retrieve the value of the "APPL_PHYSICAL_PATH" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetPhysicalPath(__out CStringA &str) throw()
    {
        return GetServerVariable("APPL_PHYSICAL_PATH", str);
    }

    // Call this function to retrieve the value of the "REMOTE_HOST" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUserHostName(__out CStringA &str) throw()
    {
        return GetServerVariable("REMOTE_HOST", str);
    }

    // Call this function to retrieve the value of the "REMOTE_ADDR" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUserHostAddress(__out CStringA &str) throw()
    {
        return GetServerVariable("REMOTE_ADDR", str);
    }

    // Call this function to retrieve the value of the "AUTH_TYPE" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetAuthenticationType(__out CStringA &str) throw()
    {
        return GetServerVariable("AUTH_TYPE", str);
    }

    // Call this function to retrieve the value of the "REMOTE_USER" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUserName(__out CStringA &str) throw()
    {
        return GetServerVariable("REMOTE_USER", str);
    }

    // Call this function to retrieve the value of the "HTTP_USER_AGENT" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUserAgent(__out CStringA &str) throw()
    {
        return GetServerVariable("HTTP_USER_AGENT", str);
    }

    // Call this function to retrieve the value of the "HTTP_ACCEPT_LANGUAGE" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUserLanguages(__out CStringA &str) throw()
    {
        return GetServerVariable("HTTP_ACCEPT_LANGUAGE", str);
    }

    // Call this function to retrieve the value of the "AUTH_USER" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetAuthUserName(__out CStringA &str) throw()
    {
        return GetServerVariable("AUTH_USER", str);
    }

    // Call this function to retrieve the value of the "AUTH_PASSWORD" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetAuthUserPassword(__out CStringA &str) throw()
    {
        return GetServerVariable("AUTH_PASSWORD", str);
    }

    // Call this function to retrieve the value of the "URL" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUrl(__out CStringA &str) throw()
    {
        return GetServerVariable("URL", str);
    }

    // Call this function to retrieve the value of the "HTTP_ACCEPT" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetAcceptTypes(__out CStringA &str) throw()
    {
        return GetServerVariable("HTTP_ACCEPT", str);
    }

    // Call this function to retrieve the value of the "HTTP_ACCEPT_ENCODING" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetAcceptEncodings(__out CStringA& str) throw()
    {
        return GetServerVariable("HTTP_ACCEPT_ENCODING", str);
    }

    // Call this function to retrieve the value of the "HTTP_REFERER" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetUrlReferer(__out CStringA &str) throw()
    {
        return GetServerVariable("HTTP_REFERER", str);
    }

    // Call this function to retrieve the value of the "SCRIPT_NAME" server variable.
    // Returns TRUE on success, FALSE on failure. Call GetLastError to get extended error information.
    __checkReturn BOOL GetScriptName(__out CStringA &str) throw()
    {
        return GetServerVariable("SCRIPT_NAME", str);
    }

    // Implementation: Call this function to populate the collection
    // of CCookie objects with the cookies in the current request.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL GetRequestCookies() throw()
    {
        BOOL bRet = FALSE;

        if(m_requestCookies.GetCount())
            return TRUE; // we already got the cookies!

        CStringA strCookies;
        if(GetCookies(strCookies))
        {
            bRet = Parse(strCookies);
        }
        return bRet;
    }

private:
    struct Pair
    {
        Pair(__in const CStringA &n, __in const CStringA& v)
        {
            name = n;
            value = v;
        }

        Pair(__in const Pair& rhs)
        {
            name = rhs.name;
            value = rhs.value;
        }

        CStringA name;
        CStringA value;
    };
    static const int INVALID_COOKIE_VERSION = -1;
public:

    // Implementation: Call this function to populate m_requestCookies
    // with a collection of CCookie objects which represents the
    // cookies contained in szCookie header sent from the browser.
    BOOL Parse(__in LPCSTR szCookieIn)
    {
        ATLENSURE(szCookieIn != NULL);
        UINT acp = GetACP();
        LPCSTR szStart;
        LPCSTR szEnd;
        CStringA name, value;
        CAtlList<Pair> pair_list;

        // create a list of name=value pairs
        // these are separated by or ;
        szStart = szCookieIn;
        szEnd = szStart;
        while(*szStart)
        {
            name.Empty();
            value.Empty();
            szStart = SkipSpace(szStart, (WORD)acp);
            if(*szStart == '\0')
                break;

            szEnd = szStart;
            while(*szEnd && *szEnd != '=' && *szEnd != ';')
                szEnd++;

            if(szEnd <= szStart)
            {
                if(*szEnd == ';')
                {
                    szEnd++;
                    szStart = szEnd;
                    continue;
                }
                szStart = szEnd;
                break; // no name or error
            }

            CopyToCString(name, szStart, szEnd);
            if(*szEnd == '\0' || *szEnd == ';')
            {
                // no value expected;
                pair_list.AddTail(Pair(name, value));
                szStart = szEnd;
            }
            else if(*szEnd == '=')
            {
                szEnd++; // skip '='
                szStart = szEnd;
                while(*szEnd && *szEnd != ';')
                    szEnd++;

                if(szEnd <= szStart)
                {
                    if(*szEnd == ';')
                    {
                        szEnd++;
                        szStart = szEnd;
                        continue;
                    }
                    szStart = szEnd;
                    pair_list.AddTail(Pair(name, value));
                    break; // no value
                }

                CopyToCString(value, szStart, szEnd);
                pair_list.AddTail(Pair(name, value));
                szStart = szEnd;
            }
        }

        // now make cookies out of the list
        // The first item could be $Version, which would
        // be the version for all cookies in the list.
        // any other $Version's found in the list will be ignored.
        if(pair_list.GetCount() <= 0)
            return TRUE; // nothing in the list

        // check for $Version
        static const char szVersion[] = "$Version";
        int nVersion = INVALID_COOKIE_VERSION;
        if(!strncmp(pair_list.GetHead().name, szVersion, sizeof(szVersion) + 1))
        {
            char *pStop = NULL;
            errno_t errnoValue = AtlStrToNum<int, char>(&nVersion, pair_list.GetHead().value, &pStop, 10);
            if(errnoValue == ERANGE)
                nVersion = INVALID_COOKIE_VERSION; // $Version contained garbage
            pair_list.RemoveHead(); // Remove $Version
        }

        POSITION pos = pair_list.GetHeadPosition();
        bool bInCookie = false;
        CCookie cookie;

        while(pos)
        {
            const Pair &p = pair_list.GetNext(pos);
            if(p.name[0] == '$')
            {
                if(!bInCookie)
                    return FALSE; // invalid cookie string
                else
                {
                    //add attribute to current cookie
                    if(!cookie.AddAttribute(p.name.Right(p.name.GetLength() - 1), p.value))
                    {
                        return FALSE;
                    }
                }
            }
            else
            {
                if(!bInCookie)
                {
                    bInCookie = true;
                    cookie.SetName(p.name);
                    cookie.ParseValue(p.value);
                    if(nVersion != INVALID_COOKIE_VERSION)
                        cookie.SetVersion(nVersion);
                }
                else
                {
                    // add previous cookie
                    CStringA strPrevName;
                    if(!cookie.GetName(strPrevName))
                    {
                        return FALSE;
                    }
                    m_requestCookies.SetAt(strPrevName, cookie);

                    // empty current cookie and start over
                    cookie.Empty();
                    cookie.SetName(p.name);
                    cookie.ParseValue(p.value);
                    if(nVersion != INVALID_COOKIE_VERSION)
                        cookie.SetVersion(nVersion);

                }
            }
        }

        if(!cookie.IsEmpty())
        {
            CStringA strName;
            if(!cookie.GetName(strName))
            {
                return FALSE;
            }
            m_requestCookies.SetAt(strName, cookie);
        }
        return TRUE;

    }

private:
    // Call this function to copy a substring to a CString reference and ensure nul-termination.
    inline void CopyToCString(__out CStringA& string, __in_ecount(pEnd - pStart) LPCSTR pStart, __in LPCSTR pEnd) throw(...)
    {
        ATLENSURE(pStart != NULL);
        ATLENSURE(pEnd != NULL);
        ATLENSURE(pEnd >= pStart);

        string.SetString(pStart, (int)(pEnd - pStart));
        string.Trim();
    }

    inline LPCSTR SkipSpace(__in LPCSTR sz, __in WORD nCodePage) throw()
    {
        if(sz == NULL)
            return NULL;

        while(isspace(static_cast<unsigned char>(*sz)))
            sz = CharNextExA(nCodePage, sz, 0);
        return sz;
    }
public:


    __success(SUCCEEDED(return)) __checkReturn HRESULT STDMETHODCALLTYPE QueryInterface(__in REFIID riid, __deref_out void **ppv)
    {
        if(!ppv)
            return E_POINTER;
        if(InlineIsEqualGUID(riid, __uuidof(IHttpRequestLookup)))
        {
            *ppv = static_cast<IUnknown*>(static_cast<IHttpRequestLookup*>(this));
            AddRef();
            return S_OK;
        }
        if(InlineIsEqualGUID(riid, __uuidof(IUnknown)))
        {
            *ppv = static_cast<IUnknown*>(this);
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return 1;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        return 1;
    }
}; // class CHttpRequest

__declspec(selectany) const char* const CHttpRequest::m_szMethodStrings[] =
{
    "GET",
    "POST",
    "HEAD",
    "DELETE",
    "LINK",
    "UNLINK",
    "DEBUG",  // Debugging support for VS7
    NULL
};

// This class provides type conversions via the Write method
// and overloaded left shift << operator for writing
// data to the IWriteStream interface. The IWriteStream interface
// only accepts strings, but this helper class allows you to transparently
// pass many different types by providing automatic type conversions.
//
// Notes on Type Conversions:
//      Numeric types are converted to their decimal representations.
//      Floating point values are output with a precision of 6 decimal places.
//      Currency values are converted according to the locale settings of the current thread.
class CWriteStreamHelper
{
protected:
    // Implementation: The IWriteStream interface.
    IWriteStream *m_pStream;
    UINT m_ulACP;

public:
    CWriteStreamHelper() throw()
        : m_pStream(NULL)
    {
        m_ulACP = _AtlGetConversionACP();
    }

    CWriteStreamHelper(__in_opt IWriteStream *pStream) throw()
    {
        m_pStream = pStream;
        m_ulACP = _AtlGetConversionACP();
    }

    // Attach a IWriteStream
    IWriteStream *Attach(__in IWriteStream *pStream) throw()
    {
        IWriteStream *pRetStream = m_pStream;
        m_pStream = pStream;
        return pRetStream;
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in_z LPCSTR szOut) throw()
    {
        ATLASSUME(m_pStream != NULL);

        if(!szOut)
            return FALSE;

        DWORD dwWritten;
        return SUCCEEDED(m_pStream->WriteStream(szOut, (int) strlen(szOut), &dwWritten));
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in int n) throw()
    {
        CHAR szTmp[21];
        Checked::itoa_s(n, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in unsigned int u) throw()
    {
        CHAR szTmp[21];
        Checked::ultoa_s(u, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in short int w) throw()
    {
        return Write((int) w);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in unsigned short int w) throw()
    {
        return Write((unsigned int) w);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in long int dw) throw()
    {
        CHAR szTmp[21];
        Checked::ltoa_s(dw, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in unsigned long int dw) throw()
    {
        CHAR szTmp[21];
        Checked::ultoa_s(dw, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in double d, __in int nDigitCount = ATL_DEFAULT_PRECISION) throw()
    {
        ATLASSUME(m_pStream != NULL);

        CHAR szTmp[512];
        int nDec = 0;
        int nSign = 0;
        bool fWriteDec = true;
        if(0 != _fcvt_s(szTmp, _countof(szTmp), d, nDigitCount, &nDec, &nSign))
        {
            // too large
            return FALSE;
        }

        if(nSign != 0)
            m_pStream->WriteStream("-", 1, NULL);
        if(nDec < 0)
        {
            nDec *= -1;
            m_pStream->WriteStream("0.", 2, NULL);
            for(int i = 0; i < nDec; i++)
            {
                m_pStream->WriteStream("0", 1, NULL);
            }
            nDec = 0;
            fWriteDec = false;
        }

        char *p = szTmp;
        while(*p)
        {
            // if the decimal lies at the end of the number
            // (no digits to the right of the decimal, we don't
            // print it.
            if(nDec == 0 && fWriteDec)
                m_pStream->WriteStream(".", 1, NULL);
            m_pStream->WriteStream(p, 1, NULL);
            nDec--;
            p++;
        }
        return TRUE;
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in __int64 i) throw()
    {
        CHAR szTmp[21];
        Checked::i64toa_s(i, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in unsigned __int64 i) throw()
    {
        CHAR szTmp[21];
        Checked::ui64toa_s(i, szTmp, _countof(szTmp), 10);
        return Write(szTmp);
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in CURRENCY c) throw()
    {
        CHAR szDest[256];
        CHAR szNumber[32];

        Checked::i64toa_s(c.int64, szNumber, _countof(szNumber), 10);
        int nLen = (int) strlen(szNumber);
        if(nLen < 5)
        {
            // prepend ascii zeros
            Checked::memmove_s(szNumber + 5 - nLen, 32 - (5 - nLen), szNumber, nLen + 1);
            memset(szNumber, '0', 5 - nLen);
            nLen = 5;
        }

        Checked::memmove_s(szNumber + nLen - 3, 32 - (nLen - 3), szNumber + nLen - 4, 5);
        szNumber[nLen-4] = '.';

        int nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, szDest, sizeof(szDest));
        if(nRet > 0)
            return Write(szDest);

        ATLASSERT(GetLastError() == ERROR_INSUFFICIENT_BUFFER);

        nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, NULL, 0);
        ATLASSERT(nRet > 0);

        if(nRet <= 0)
            return FALSE;

        CAutoVectorPtr<CHAR> szBuffer;
        if(!szBuffer.Allocate(nRet))
        {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        }
        nRet = GetCurrencyFormatA(GetThreadLocale(), 0, szNumber, NULL, szBuffer, nRet);

        ATLASSERT(nRet > 0);
        BOOL bRet = FALSE;
        if(nRet > 0)
            bRet = Write(szBuffer);

        return bRet;
    }

    // Call this function to write data to the IWriteStream interface managed by this object.
    // Returns TRUE on success, FALSE on failure.
    BOOL Write(__in LPCWSTR wsz) throw()
    {
        ATLASSUME(m_pStream != NULL);

        BOOL bRet;

        _ATLTRY
        {
            CW2A sz(wsz, m_ulACP);

            if(!sz)
            {
                bRet = FALSE;
            }

            DWORD dwWritten;
            bRet = SUCCEEDED(m_pStream->WriteStream(sz, (int) strlen(sz), &dwWritten));
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }

        return bRet;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in LPCSTR szStr) throw(...)
    {
        if(!Write(szStr))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in LPCWSTR wszStr) throw(...)
    {
        if(!Write(wszStr))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in int n) throw(...)
    {
        if(!Write(n))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in short int w) throw(...)
    {
        if(!Write(w))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in unsigned int u) throw(...)
    {
        if(!Write(u))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in long int dw) throw(...)
    {
        if(!Write(dw))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in unsigned long int dw) throw(...)
    {
        if(!Write(dw))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in double d) throw(...)
    {
        if(!Write(d))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in __int64 i) throw(...)
    {
        if(!Write(i))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in unsigned __int64 i) throw(...)
    {
        if(!Write(i))
            AtlThrow(E_FAIL);
        return *this;
    }

    // Use this operator to write data to the IWriteStream interface managed by this object.
    CWriteStreamHelper& operator<<(__in CURRENCY c) throw(...)
    {
        if(!Write(c))
            AtlThrow(E_FAIL);
        return *this;
    }

    UINT SetConversionCodePage(__in UINT nNewCP)
    {
        UINT nOldCP = m_ulACP;
        m_ulACP = nNewCP;
        return nOldCP;
    }
};


template < DWORD dwSizeT = ATL_ISAPI_BUFFER_SIZE >
class CAtlIsapiBuffer
{
protected:
    char m_szBuffer[dwSizeT];
    LPSTR m_pBuffer;
    DWORD m_dwLen;
    DWORD m_dwAlloc;
    HANDLE m_hProcHeap;

public:
    CAtlIsapiBuffer() throw()
    {
        if(dwSizeT > 0)
            m_szBuffer[0] = 0;

        m_pBuffer = m_szBuffer;
        m_dwLen = 0;
        m_dwAlloc = dwSizeT;
        m_hProcHeap = GetProcessHeap();
    }

    CAtlIsapiBuffer(__in LPCSTR sz)
    {
        m_pBuffer = m_szBuffer;
        m_dwLen = 0;
        m_dwAlloc = dwSizeT;
        m_hProcHeap = GetProcessHeap();

        if(!Append(sz))
            AtlThrow(E_OUTOFMEMORY);
    }

    ~CAtlIsapiBuffer() throw()
    {
        Free();
    }

    BOOL Alloc(__in DWORD dwSize) throw()
    {
        if(m_dwAlloc >= dwSize)
        {
            return TRUE;
        }
        if(m_pBuffer != m_szBuffer)
        {
            HeapFree(m_hProcHeap, 0, m_pBuffer);
            m_dwLen = 0;
            m_dwAlloc = 0;
        }
        m_pBuffer = (LPSTR)HeapAlloc(m_hProcHeap, 0, dwSize);
        if(m_pBuffer)
        {
            m_dwAlloc = dwSize;
            return TRUE;
        }
        return FALSE;
    }

    BOOL ReAlloc(__in DWORD dwNewSize) throw()
    {
        if(dwNewSize <= m_dwAlloc)
            return TRUE;

        if(m_pBuffer == m_szBuffer)
        {
            BOOL bRet = Alloc(dwNewSize);
            if(bRet)
            {
                Checked::memcpy_s(m_pBuffer, dwNewSize, m_szBuffer, m_dwLen);
            }
            return bRet;
        }

        LPSTR pvNew = (LPSTR)HeapReAlloc(m_hProcHeap, 0, m_pBuffer, dwNewSize);
        if(pvNew)
        {
            m_pBuffer = pvNew;
            m_dwAlloc = dwNewSize;
            return TRUE;
        }
        return FALSE;
    }

    void Free() throw()
    {
        if(m_pBuffer != m_szBuffer)
        {
            HeapFree(m_hProcHeap, 0 , m_pBuffer);
            m_dwAlloc = dwSizeT;
            m_pBuffer = m_szBuffer;
        }
        Empty();
    }

    void Empty() throw()
    {
        if(m_pBuffer)
        {
            m_pBuffer[0] = 0;
            m_dwLen  = 0;
        }
    }

    DWORD GetLength() throw()
    {
        return m_dwLen;
    }

    BOOL Append(__in LPCSTR sz, __in int nLen = -1) throw()
    {
        if(!sz)
            return FALSE;

        if(nLen == -1)
            nLen = (int) strlen(sz);

        DWORD newLen = m_dwLen + nLen + 1;
        if(newLen <= m_dwLen || newLen <= (DWORD)nLen)
        {
            return FALSE;
        }
        if(newLen > m_dwAlloc)
        {
            if(!ReAlloc(m_dwAlloc + (nLen + 1 > ATL_ISAPI_BUFFER_SIZE ? nLen + 1 : ATL_ISAPI_BUFFER_SIZE)))
                return FALSE;
        }
        Checked::memcpy_s(m_pBuffer + m_dwLen, m_dwAlloc - m_dwLen, sz, nLen);
        m_dwLen += nLen;
        m_pBuffer[m_dwLen] = 0;
        return TRUE;
    }

    operator LPCSTR() throw()
    {
        return m_pBuffer;
    }

    CAtlIsapiBuffer& operator+=(__in LPCSTR sz)
    {
        if(!Append(sz))
            AtlThrow(E_OUTOFMEMORY);
        return *this;
    }
}; // class CAtlIsapiBuffer

// This class represents the response that the web server will send back to the client.
//
// CHttpResponse provides friendly functions for building up the headers, cookies, and body of an HTTP response.
// The class derives from IWriteStream and CWriteStreamHelper, allowing you to call those classes' methods
// to build up the body of the response. By default, the class improves performance by buffering the response until it is complete before sending it back to the client.
class CHttpResponse : public IWriteStream, public CWriteStreamHelper
{
private:

    // Implementation: A map of HTTP response headers.
    // The key is the name of the response header.
    // The value is the data for the response header.
    CSimpleMap<CStringA, CStringA> m_headers;

    // Implementation: Determines whether the response is currently being buffered.
    BOOL m_bBufferOutput;

    // Implementation: Determines whether any output should be sent to the client.
    // Intended mainly for HEAD requests, where the client should get the same headers
    // (i.e. Content-Length) as for a GET request
    BOOL m_bSendOutput;

    // Implementation: The limit in bytes of the response buffer.
    // When the limit is reached, the buffer is automatically flushed
    // and data is sent to the client. You can set this to ULONG_MAX
    // to enable full buffering (this is the default, and is required
    // for enabling keep alive connections).
    DWORD m_dwBufferLimit;

    // Implementation: The server context.
    CComPtr<IHttpServerContext> m_spServerContext;

    // Implementation: The HTTP status code for the response.
    int m_nStatusCode;

    // Implementation: Determines whether the response headers have already been sent to the client.
    BOOL m_bHeadersSent;

    // Implementation: Handle of the file being transmitted so it can be closed
    // when the async I/O completes
    HANDLE m_hFile;

public:
    // Implementation: The buffer used to store the response before
    // the data is sent to the client.
    CAtlIsapiBuffer<> m_strContent;

    // Numeric constants for the HTTP status codes used for redirecting client requests.
    enum HTTP_REDIRECT
    {
        HTTP_REDIRECT_MULTIPLE = 300,
        HTTP_REDIRECT_MOVED = 301,
        HTTP_REDIRECT_FOUND = 302,
        HTTP_REDIRECT_SEE_OTHER = 303,
        HTTP_REDIRECT_NOT_MODIFIED = 304,
        HTTP_REDIRECT_USE_PROXY = 305,
        HTTP_REDIRECT_TEMPORARY_REDIRECT = 307
    };

    CHttpResponse() throw()
    {
        m_bBufferOutput = TRUE;
        m_dwBufferLimit = ULONG_MAX;
        m_nStatusCode = 200;
        m_pStream = this;
        m_bHeadersSent = FALSE;
        m_bSendOutput = TRUE;
        m_hFile = INVALID_HANDLE_VALUE;
    }

    CHttpResponse(__in IHttpServerContext *pServerContext)
    {
        m_bBufferOutput = TRUE;
        m_dwBufferLimit = ULONG_MAX;
        m_nStatusCode = 200;
        m_pStream = this;
        m_bHeadersSent = FALSE;
        ATLENSURE(Initialize(pServerContext));
        m_bSendOutput = TRUE;
        m_hFile = INVALID_HANDLE_VALUE;
    }

    // The destructor flushes the buffer if there is content that
    // hasn't yet been sent to the client.
    virtual ~CHttpResponse() throw()
    {
        Flush(TRUE);
        if(m_hFile && m_hFile != INVALID_HANDLE_VALUE)
            CloseHandle(m_hFile);
    }

    // Call this function to initialize the response object with a pointer to the server context.
    // Returns TRUE on success, FALSE on failure.
    __checkReturn BOOL Initialize(__in IHttpServerContext *pServerContext) throw()
    {
        ATLASSERT(pServerContext != NULL);
        if(!pServerContext)
            return FALSE;

        m_spServerContext = pServerContext;

        return TRUE;
    }

    // This is called to initialize the CHttpResponse for a child handler.  By default, it
    // assumes the parent will be responsible for sending the headers.
    __checkReturn BOOL Initialize(__in IHttpRequestLookup *pLookup) throw(...)
    {
        ATLASSERT(pLookup);
        if(!pLookup)
            return FALSE;

        CComPtr<IHttpServerContext> spContext;
        HRESULT hr = pLookup->GetServerContext(&spContext);
        if(FAILED(hr))
            return FALSE;

        if(!Initialize(spContext))
            return FALSE;

        m_bHeadersSent = TRUE;

        return TRUE;
    }

    // Returns a pointer to the IHttpServerContext interface for the current request.
    __checkReturn HRESULT GetServerContext(__deref_out_opt IHttpServerContext ** ppOut) throw()
    {
        return m_spServerContext.CopyTo(ppOut);
    }

    void Detach()
    {
        m_spServerContext.Release();
        HaveSentHeaders(TRUE);
    }
    // Call this function to set buffering options for the response.
    //
    // This function allows you to turn buffering on or off, and to set a size limit
    // on the amount of data that will be buffered before being sent to the client.
    //
    // When you turn off buffering, the current contents of the buffer will be sent to the client.
    // If you need to clear the buffer without sending the contents to the client, call ClearContent instead.
    //
    // When the size of the buffer is reduced below the current size of the buffered content,
    // the entire buffer is flushed.
    void SetBufferOutput(__in BOOL bBufferOutput, __in DWORD dwBufferLimit = ATL_ISAPI_BUFFER_SIZE) throw()
    {
        if(m_bBufferOutput && !bBufferOutput)
        {
            // before turning off buffering, flush
            // the current contents
            Flush();
        }
        SetBufferLimit(dwBufferLimit);

        m_bBufferOutput = bBufferOutput;
    }

    // Call this function to determine whether data written to the response object is being buffered or not.
    // Returns TRUE if output is being buffered, FALSE otherwise.
    __checkReturn BOOL GetBufferOutput() throw()
    {
        return m_bBufferOutput;
    }

    // Call this function to determine whether the response headers have been sent
    // Returns TRUE if headers have been sent, FALSE otherwise.
    __checkReturn BOOL HaveSentHeaders() throw()
    {
        return m_bHeadersSent;
    }

    // Call this function to override the m_bHeadersSent state.  This is useful
    // when you want child handlers (e.g. from an include or subhandler) to send the headers
    void HaveSentHeaders(__in BOOL bSent) throw()
    {
        m_bHeadersSent = bSent;
    }

    // Call this function to set a size limit on the amount of data buffered by the reponse object.
    // When the size of the buffer is reduced below the current size of the buffered content,
    // the entire buffer is flushed.
    // See GetBufferLimit.
    void SetBufferLimit(__in DWORD dwBufferLimit) throw()
    {
        if(m_bBufferOutput)
        {
            if(m_strContent.GetLength() >= dwBufferLimit)
            {
                // new buffer limit is less than the
                // size currently buffered.  So flush
                // the current buffer
                Flush();
            }
        }
        m_dwBufferLimit = dwBufferLimit;
    }

    // Returns the current size limit of the buffer in bytes.
    // See SetBufferLimit.
    DWORD GetBufferLimit() throw()
    {
        return m_dwBufferLimit;
    }

    // Returns the current value of the Content-Type header if present, otherwise returns NULL.
    LPCSTR GetContentType() throw()
    {
        // return the content type from the
        // header collection if any
        _ATLTRY
        {
            CStringA strKey("Content-Type");

            int nIndex = m_headers.FindKey(strKey);
            if(nIndex >= 0)
                return m_headers.GetValueAt(nIndex);
        }
        _ATLCATCHALL()
        {
        }
        return NULL;
    }

    // Call this function to set the Content-Type of the HTTP response.
    // Examples of common MIME content types include text/html and text/plain.
    BOOL SetContentType(__in_opt LPCSTR szContentType) throw()
    {
        _ATLTRY
        {
            if(!m_headers.SetAt("Content-Type", szContentType))
                return m_headers.Add("Content-Type", szContentType);
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to set the HTTP status code of the response.
    // If not set explicitly, the default status code is 200 (OK).
    // See GetStatusCode.
    void SetStatusCode(__in int nCode) throw()
    {
        m_nStatusCode = nCode;
    }

    // Returns the current HTTP status code of the response.
    // See SetStatusCode.
    int GetStatusCode() throw()
    {
        return m_nStatusCode;
    }

    // Call this function to set the Cache-Control http header of the response.
    // Examples of common Cache-Control header values: public, private, max-age=delta-seconds
    BOOL SetCacheControl(__in_opt LPCSTR szCacheControl) throw()
    {
        _ATLTRY
        {
            if(!m_headers.SetAt("Cache-Control", szCacheControl))
                return m_headers.Add("Cache-Control", szCacheControl);
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to set the Expires HTTP header to the absolute date/time
    // specified in the stExpires parameter
    BOOL SetExpiresAbsolute(__in const SYSTEMTIME& stExpires) throw()
    {
        _ATLTRY
        {
            CStringA strExpires;
            SystemTimeToHttpDate(stExpires, strExpires);

            if(!m_headers.SetAt("Expires", strExpires))
                return m_headers.Add("Expires", strExpires);
        }
        _ATLCATCHALL()
        {
        }
        return FALSE;
    }

    // Call this function to set the Expires HTTP header to a relative date/time
    // value specified in minutes;
    BOOL SetExpires(__in long lMinutes) throw()
    {
        CFileTime ft;
        GetSystemTimeAsFileTime(&ft);

        // add the specified number of minutes
        ft += CFileTimeSpan(((ULONGLONG)lMinutes) * 60 * 10000000);

        SYSTEMTIME st;
        FileTimeToSystemTime(&ft, &st);
        return SetExpiresAbsolute(st);
    }

    // Call this function to set whether or not to output to client.
    // Intended primarily for HEAD requests
    BOOL SetWriteToClient(__in BOOL bSendOutput) throw()
    {
        m_bSendOutput = bSendOutput;
        return TRUE;
    }

    // Call this function to determine whether or not the data is
    // sent to the client.  Intended primarily for HEAD requests
    BOOL GetWriteToClient() throw()
    {
        return m_bSendOutput;
    }

    // Call this function to write data to the response object.
    //
    // Returns S_OK on success, E_INVALIDARG or E_FAIL on failure.
    //
    // See WriteClient for comments on buffering.
    //
    // szOut    A pointer to the first byte of the data to be written.
    //
    // nLen     The number of bytes to write. If this parameter is -1,
    //          szOut is assumed to be a nul-terminated string and the
    //          whole string will be written.
    //
    // pdwWritten   A DWORD pointer that can be used to get the number of bytes written.
    //              This parameter can be NULL.
    __checkReturn HRESULT WriteStream(__in_z LPCSTR szOut, __in int nLen, __out_opt DWORD *pdwWritten)
    {
        ATLASSUME(m_spServerContext != NULL);

        if(pdwWritten)
            *pdwWritten = 0;
        if(nLen == -1)
        {
            if(!szOut)
                return E_INVALIDARG;
            nLen = (int) strlen(szOut);
        }
        BOOL bRet = WriteLen(szOut, nLen);
        if(!bRet)
        {
            return AtlHresultFromLastError();
        }
        if(pdwWritten)
            *pdwWritten = nLen;
        return S_OK;
    }

    // Call this function to write data to the response object.
    //
    // Returns TRUE on success. FALSE on failure.
    //
    // If buffering is disabled, data is written directly to the client.
    // If buffering is enabled, this function attempts to write to the buffer.
    // If the buffer is too small to contain its existing data and the new data,
    // the current contents of the buffer are flushed.
    // If the buffer is still too small to contain the new data, that data is written
    // directly to the client. Otherwise the new data is written to the buffer.
    //
    // Any headers that have been set in the response will be sent just before the
    // data is written to the client if no headers have been sent up to that point.
    //
    // szOut    A pointer to the first byte of the data to be written.
    //
    // nLen     The number of bytes to write.
    __checkReturn BOOL WriteLen(__in_ecount(dwLen) LPCSTR szOut, __in DWORD dwLen) throw()
    {
        ATLASSUME(m_spServerContext != NULL);
        if(!szOut)
            return FALSE;

        if(m_bBufferOutput)
        {
            if(m_strContent.GetLength() + dwLen >= m_dwBufferLimit)
            {
                if(!Flush())
                    return FALSE;
            }
            if(dwLen <= m_dwBufferLimit)
                return m_strContent.Append(szOut, dwLen);
        }
        BOOL bRet = SendHeadersInternal();

        if(bRet && m_bSendOutput)
            bRet = m_spServerContext->WriteClient((void *) szOut, &dwLen);

        return bRet;
    }

    // Call this function to redirect the client to a different resource.
    //
    // Returns TRUE on success, FALSE on failure.
    //
    // szURL        A nul-terminated string specifying the resource the client should navigate to.
    //
    // statusCode   An HTTP status code from the HTTP_REDIRECT enumeration describing the reason
    //              for the redirection.
    //
    // bSendBody    Specifies whether to generate and send a response body with the headers.
    //
    //  This function allows (and RFC 2616 encourages) a response body to be sent
    //  with the following redirect types:
    //      HTTP_REDIRECT_MOVED
    //      HTTP_REDIRECT_FOUND
    //      HTTP_REDIRECT_SEE_OTHER
    //      HTTP_REDIRECT_TEMPORARY_REDIRECT
    // No body will be sent with other redirect types.
    //
    // The response body contains a short hypertext note with a hyperlink to the new resource.
    // A meta refresh tag is also included to allow browsers to automatically redirect
    // the user to the resource even if they don't understand the redirect header.
    //
    // See RFC 2616 section 10.3 for more information on redirection.
    BOOL Redirect(__in LPCSTR szUrl, __in HTTP_REDIRECT statusCode = HTTP_REDIRECT_MOVED, __in BOOL bSendBody = TRUE) throw(...)
    {
        CStringA strBody;
        LPCSTR szBody = NULL;
        if(bSendBody &&
           (HTTP_REDIRECT_MOVED == statusCode  || HTTP_REDIRECT_FOUND == statusCode ||
            HTTP_REDIRECT_SEE_OTHER == statusCode || HTTP_REDIRECT_TEMPORARY_REDIRECT == statusCode))
        {
            _ATLTRY
            {
                ATLENSURE(szUrl != NULL);
                strBody.Format(
                    "<html>\r\n"
                    "<head>\r\n"
                    "<meta http-equiv=\"refresh\" content=\"0; url=%s\">\r\n"
                    "</head>\r\n"
                    "<body>Please use the following link to access this resource:"
                    " <a href=\"%s\">%s</a>\r\n"
                    "</body>\r\n"
                    "</html>\r\n",
                    szUrl, szUrl, szUrl);
            }
            _ATLCATCHALL()
            {
                return FALSE;
            }
            szBody = (LPCSTR) strBody;
        }
        return Redirect(szUrl, szBody, statusCode);
    }

    // Call this function to redirect the client to a different resource.
    //
    // Returns TRUE on success, FALSE on failure.
    //
    // szURL        A nul-terminated string specifying the resource the client should navigate to.
    //
    // szBody       A nul-terminated string containing the body of the response to be sent to the client.
    //
    // statusCode   An HTTP status code from the HTTP_REDIRECT enumeration describing the reason
    //              for the redirection.
    //
    //  This function allows (and RFC 2616 encourages) a response body to be sent
    //  with the following redirect types:
    //      HTTP_REDIRECT_MOVED
    //      HTTP_REDIRECT_FOUND
    //      HTTP_REDIRECT_SEE_OTHER
    //      HTTP_REDIRECT_TEMPORARY_REDIRECT
    // No body will be sent with other redirect types.
    //
    // The response body should contain a short hypertext note with a hyperlink to the new resource.
    // You can include a meta refresh tag to allow browsers to automatically redirect
    // the user to the resource even if they don't understand the redirect header.
    //
    // See RFC 2616 section 10.3 for more information on redirection.
    BOOL Redirect(__in LPCSTR szUrl, __in LPCSTR szBody, __in HTTP_REDIRECT statusCode = HTTP_REDIRECT_MOVED) throw()
    {
        SetStatusCode(statusCode);
        AppendHeader("Location", szUrl);

        _ATLTRY
        {
            if(!SendHeadersInternal())
                return FALSE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }

        if(szBody &&
           (HTTP_REDIRECT_MOVED == statusCode  || HTTP_REDIRECT_FOUND == statusCode ||
            HTTP_REDIRECT_SEE_OTHER == statusCode || HTTP_REDIRECT_TEMPORARY_REDIRECT == statusCode))
        {
            Write(szBody);
            return Flush();
        }
        return TRUE;
    }

    // Call this function to append a header to the collection of HTTP headers managed by this object.
    //
    // szName   A nul-teminated string containing the name of the HTTP header.
    //
    // szValue  A nul-teminated string containing the value of the HTTP header.
    BOOL AppendHeader(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
    {
        ATLASSERT(szName);
        BOOL bRet = FALSE;
        _ATLTRY
        {
            bRet = m_headers.Add(szName, szValue);
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;
    }

    // Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
    //
    // pCookie      A pointer to a CCookie object describing the cookie to be sent to the client.
    BOOL AppendCookie(__in const CCookie *pCookie)
    {
        ATLENSURE(pCookie);
        return AppendCookie((const CCookie&) * pCookie);
    }

    // Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
    //
    // cookie       A reference to a CCookie object describing the cookie to be sent to the client.
    BOOL AppendCookie(__in const CCookie& cookie) throw()
    {
        CHAR szCookie[ATL_MAX_COOKIE_LEN];
        DWORD dwBuffSize = ATL_MAX_COOKIE_LEN;
        BOOL bRet = FALSE;
        bRet = cookie.Render(szCookie, &dwBuffSize);
        if(bRet)
        {
            _ATLTRY
            {
                bRet = m_headers.Add("Set-Cookie", szCookie);
            }
            _ATLCATCHALL()
            {
                bRet = FALSE;
                dwBuffSize = 0;
            }
        }

        if(!bRet && dwBuffSize > 0 && dwBuffSize + 1 > dwBuffSize)   //static buffer wasn't big enough.
        {
            //We'll have to try dynamically allocating it
            //allocate a buffer
            CAutoVectorPtr<CHAR> sz;
            if(sz.Allocate(dwBuffSize + 1))
            {
                DWORD dwSizeNew = dwBuffSize + 1;
                if(cookie.Render(sz, &dwSizeNew))
                {
                    _ATLTRY
                    {
                        bRet = m_headers.Add("Set-Cookie", (const char *) sz);
                    }
                    _ATLCATCHALL()
                    {
                        bRet = FALSE;
                    }
                }
            }
        }
        return bRet;
    }

    // Call this function to add a Set-Cookie header to the collection of HTTP headers managed by this object.
    //
    // szName       A nul-terminated string containing the name of the cookie to be sent to the client.
    //
    // szValue      A nul-terminated string containing the value of the cookie to be sent to the client.
    BOOL AppendCookie(__in LPCSTR szName, __in_opt LPCSTR szValue) throw()
    {
        ATLASSERT(szName);
        BOOL bRet = FALSE;
        _ATLTRY
        {
            CCookie c(szName, szValue);
            bRet = AppendCookie(c);
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;
    }

    // Call this function to add a Set-Cookie header that removes a cookie value
    // to the collection of HTTP headers managed by this object.
    //
    // szName       A nul-terminated string containing the name of the cookie to be deleted
    BOOL DeleteCookie(__in LPCSTR szName) throw()
    {
        ATLASSERT(szName);
        BOOL bRet = FALSE;
        _ATLTRY
        {
            CCookie cookie(szName, "");
            bRet = cookie.SetMaxAge(0);
            if(bRet)
            {
                bRet = AppendCookie(cookie);
            }
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;

    }

    // Call this function to clear the collection of HTTP response headers maintained by this object.
    //
    // Note that clearing the headers includes removing all cookies associated with the response
    // object. Cookies are sent to the client as Set-Cookie HTTP headers.
    void ClearHeaders() throw()
    {
        m_headers.RemoveAll();
    }

    // Call this function to clear theresponse buffer without sending the contents to the client.
    // If you need to empty the buffer but you do want the current contents sent to the client, call Flush instead.
    void ClearContent() throw()
    {
        m_strContent.Empty();
    }

    // Call this function to send the current headers associated with this object to the client.
    //
    // Returns TRUE on success, FALSE on failure.
    //
    // The response headers are sent to the client using the current status code for the
    // response object. See SetStatusCode and GetStatusCode.
    BOOL SendHeadersInternal(__in BOOL fKeepConn = FALSE)
    {
        if(m_bHeadersSent)
            return TRUE;

        ATLENSURE(m_spServerContext != NULL);

        CStringA strHeaders;
        RenderHeaders(strHeaders);

        BOOL bRet = FALSE;
        _ATLTRY
        {
            if(m_nStatusCode == 200)
            {
                bRet = m_spServerContext->SendResponseHeader(strHeaders, "200 OK", fKeepConn);
                if(bRet)
                {
                    m_bHeadersSent = TRUE;
                }
                return bRet;
            }

            CFixedStringT<CStringA, 256> strStatus;
            CDefaultErrorProvider prov;
            GetStatusHeader(strStatus, m_nStatusCode, SUBERR_NONE, &prov);
            bRet = m_spServerContext->SendResponseHeader(strHeaders, strStatus, fKeepConn);
            if(bRet)
            {
                m_bHeadersSent = TRUE;
            }
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;
    }

    // Call this function to get a string containing all the HTTP headers associated with
    // this object in a format suitable for sending to a client.
    //
    // strHeaders   A CStringA reference to which will be appended the HTTP headers.
    void RenderHeaders(CStringA& strHeaders) throw()
    {
        _ATLTRY
        {
            strHeaders.Preallocate(::ATL::AtlMultiplyThrow(m_headers.GetSize(), 64));
            for(int i = 0; i < m_headers.GetSize(); i++)
            {
                strHeaders += m_headers.GetKeyAt(i);
                strHeaders.Append(": ", sizeof(": ") - 1);
                strHeaders += m_headers.GetValueAt(i);
                strHeaders.Append("\r\n", sizeof("\r\n") - 1);
            }
            strHeaders.Append("\r\n", sizeof("\r\n") - 1);
        }
        _ATLCATCHALL()
        {
        }
    }

    // Call this function to empty the response buffer and send its current
    // contents to the client.
    //
    // Returns S_OK on success, or an error HRESULT on failure.
    HRESULT FlushStream()
    {
        if(!Flush())
            return AtlHresultFromLastError();
        return S_OK;
    }

    // Call this function to empty the response buffer and send its current
    // contents to the client.
    //
    // Returns TRUE on success, or FALSE on failure.
    //
    // Any headers that have been set in the response will be sent just before the
    // data is written to the client if no headers have been sent up to that point.
    BOOL Flush(BOOL bFinal = FALSE) throw()
    {
        if(!m_spServerContext)
            return FALSE;

        BOOL bRet = TRUE;

        _ATLTRY
        {
            // if the headers haven't been sent,
            // send them now

            if(!m_bHeadersSent)
            {
                char szProtocol[ATL_URL_MAX_URL_LENGTH];
                DWORD dwProtocolLen = sizeof(szProtocol);

                if(bFinal && m_bBufferOutput && m_dwBufferLimit == ULONG_MAX)
                {
                    if(m_spServerContext->GetServerVariable("SERVER_PROTOCOL", szProtocol, &dwProtocolLen) &&
                    !strcmp(szProtocol, "HTTP/1.0"))
                        AppendHeader("Connection", "Keep-Alive");
                    Checked::itoa_s(m_strContent.GetLength(), szProtocol, _countof(szProtocol), 10);
                    AppendHeader("Content-Length", szProtocol);
                    bRet = SendHeadersInternal(TRUE);
                }
                else
                    bRet = SendHeadersInternal();
            }
            if(m_bBufferOutput)
            {
                DWORD dwLen = 0;

                dwLen = m_strContent.GetLength();
                if(dwLen)
                {
                    if(m_bSendOutput &&
                    m_spServerContext->WriteClient((void *)(LPCSTR) m_strContent, &dwLen) != TRUE)
                    {
                        m_strContent.Empty();
                        return FALSE;
                    }
                    m_strContent.Empty();
                }
            }
        } // _ATLTRY
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }
        return bRet;
    }

    // Call this function to clear the response object of any headers
    // and the contents of the buffer.
    void ClearResponse() throw()
    {
        m_strContent.Empty();
        m_headers.RemoveAll();
    }

    BOOL AsyncPrep(__in BOOL fKeepConn = FALSE) throw()
    {
        ATLASSUME(m_spServerContext != NULL);

        return SendHeadersInternal(fKeepConn);
    }

    BOOL AsyncFlush() throw()
    {
        ATLASSUME(m_spServerContext != NULL);

        BOOL bRet = SendHeadersInternal();

        if(bRet && m_bBufferOutput)
        {
            DWORD dwLen = 0;

            dwLen = m_strContent.GetLength();
            if(dwLen)
            {
                _ATLTRY
                {
                    if(m_spServerContext->AsyncWriteClient((void *)(LPCSTR) m_strContent, &dwLen) != TRUE)
                    {
                        bRet = FALSE;
                    }
                }
                _ATLCATCHALL()
                {
                    bRet = FALSE;
                }
            }
        }

        return bRet;
    }

    BOOL TransmitFile(__in HANDLE hFile, __in LPCSTR szContentType = "text/plain") throw()
    {
        ATLASSUME(m_spServerContext != NULL);
        ATLASSERT(hFile != NULL && hFile != INVALID_HANDLE_VALUE);

        SetContentType(szContentType);

        if(m_strContent.GetLength())
            if(!Flush())
                return FALSE;

        BOOL bRet = SendHeadersInternal();
        if(bRet)
        {
            _ATLTRY
            {
                bRet = m_spServerContext->TransmitFile(hFile, NULL, NULL, NULL,
                0, 0, NULL, 0, NULL, 0, HSE_IO_ASYNC | HSE_IO_NODELAY);
            }
            _ATLCATCHALL()
            {
                bRet = FALSE;
            }
        }

        return bRet;
    }
}; // class CHttpResponse



#define ATLS_FLAG_NONE      0
#define ATLS_FLAG_ASYNC     1   // handler might do some async handling

// push_macro/pop_macro doesn't work in a template definition.
#pragma push_macro("new")
#undef new
template <class T>
class PerThreadWrapper : public CComObjectNoLock<T>
{
public:
    void *operator new(size_t /*size*/, void *p) throw()
    {
        return p;
    }

    void operator delete(void * /*p*/) throw()
    {
    }

    STDMETHOD_(ULONG, Release)() throw()
    {
        ULONG l = InternalRelease();
        if(l == 0)
        {
            T *pT = static_cast<T*>(this);
            ATLASSERT(pT->m_spExtension != NULL);
            CIsapiWorker *pWorker = pT->m_spExtension->GetThreadWorker();
            ATLASSERT(pWorker);

            delete this;
            if(pWorker)
            {
                HeapFree(pWorker->m_hHeap, HEAP_NO_SERIALIZE, this);
            }
        }
        return l;
    }
};

template <typename THandler>
inline BOOL CreateRequestHandlerSync(__in IIsapiExtension *pExtension, __deref_out_opt IUnknown **ppOut)
{
    ATLENSURE(pExtension);
    ATLENSURE(ppOut);

    CIsapiWorker *pWorker = pExtension->GetThreadWorker();
    ATLENSURE(pWorker);
    void *pv = HeapAlloc(pWorker->m_hHeap, HEAP_NO_SERIALIZE, sizeof(PerThreadWrapper<THandler>));
    if(!pv)
        return FALSE;

    PerThreadWrapper<THandler> *pHandler = new(pv) PerThreadWrapper<THandler>;
    *ppOut = static_cast<IRequestHandler *>(pHandler);
    pHandler->m_spExtension = pExtension;

    (*ppOut)->AddRef();

    return TRUE;
}
#pragma pop_macro("new")

#define DECLARE_ASYNC_HANDLER() \
	static DWORD GetHandlerFlags() \
	{ \
		return ATLS_FLAG_ASYNC; \
	} \
	DWORD GetAsyncFlags() \
	{ \
		return ATLSRV_INIT_USEASYNC; \
	}

#define DECLARE_ASYNC_HANDLER_EX() \
	static DWORD GetHandlerFlags() \
	{ \
		return ATLS_FLAG_ASYNC; \
	} \
	DWORD GetAsyncFlags() \
	{ \
		return (ATLSRV_INIT_USEASYNC|ATLSRV_INIT_USEASYNC_EX); \
	}


template <typename THandler>
class IRequestHandlerImpl : public IRequestHandler
{
public:
    HINSTANCE m_hInstHandler;
    CComPtr<IServiceProvider> m_spServiceProvider;
    CComPtr<IHttpServerContext> m_spServerContext;
    CComPtr<IIsapiExtension> m_spExtension;
    DWORD m_dwAsyncFlags;

    IRequestHandlerImpl()
        : m_hInstHandler(NULL)
    {
        m_dwAsyncFlags = 0;
    }
    virtual ~IRequestHandlerImpl()
    {
    }

    HTTP_CODE GetFlags(__out DWORD *pdwStatus)
    {
        ATLASSERT(pdwStatus);
        THandler *pT = static_cast<THandler *>(this);

        *pdwStatus = pT->GetAsyncFlags();
        if(pT->CachePage())
            *pdwStatus |= ATLSRV_INIT_USECACHE;

#ifdef _DEBUG
        if(*pdwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
            ATLASSERT(pT->GetHandlerFlags() & ATLS_FLAG_ASYNC);
#endif

        return HTTP_SUCCESS;
    }

    HTTP_CODE InitializeHandler(__in AtlServerRequest *pRequestInfo, __in IServiceProvider *pProvider)
    {
        ATLENSURE(pRequestInfo != NULL);
        ATLENSURE(pProvider != NULL);
        ATLENSURE(pRequestInfo->hInstDll != NULL);
        ATLENSURE(pRequestInfo->pServerContext != NULL);

        // Initialize our internal references to required services
        m_hInstHandler = pRequestInfo->hInstDll;
        m_spServiceProvider = pProvider;
        m_spServerContext = pRequestInfo->pServerContext;

        return HTTP_SUCCESS;
    }

    HTTP_CODE InitializeChild(__in AtlServerRequest *pRequestInfo, __in IServiceProvider *pProvider, IHttpRequestLookup * /*pLookup*/)
    {
        return InitializeHandler(pRequestInfo, pProvider);
    }

    void UninitializeHandler()
    {
    }

    HTTP_CODE HandleRequest(
        AtlServerRequest* /*pRequestInfo*/,
        IServiceProvider* /*pServiceProvider*/)
    {
        return HTTP_SUCCESS;
    }

    DWORD GetAsyncFlags()
    {
        return m_dwAsyncFlags;
    }

    void SetAsyncFlags(__in DWORD dwAsyncFlags)
    {
        ATLASSERT((dwAsyncFlags & ~(ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX)) == 0);
        m_dwAsyncFlags = dwAsyncFlags;
    }

    BOOL CachePage()
    {
        return FALSE;
    }

    static DWORD GetHandlerFlags()
    {
        return ATLS_FLAG_NONE;
    }

    // Used to create new instance of this object. A pointer to this
    // function is stored in the handler map in user's code.
    static BOOL CreateRequestHandler(__in IIsapiExtension *pExtension, __deref_out_opt IUnknown **ppOut)
    {
        ATLASSERT(ppOut != NULL);
        if(ppOut == NULL)
            return false;

        *ppOut = NULL;

        if(THandler::GetHandlerFlags() & ATLS_FLAG_ASYNC)
        {
            THandler *pHandler = NULL;
            ATLTRY(pHandler = new CComObjectNoLock<THandler>);
            if(!pHandler)
                return FALSE;
            *ppOut = static_cast<IRequestHandler *>(pHandler);
            pHandler->m_spExtension = pExtension;
            (*ppOut)->AddRef();
        }
        else
        {
            if(!CreateRequestHandlerSync<THandler>(pExtension, ppOut))
                return FALSE;
        }

        return TRUE;
    }

    // Used to initialize the class
    // function is stored in the handler map in user's code.
    static BOOL InitRequestHandlerClass(IHttpServerContext *pContext, IIsapiExtension *pExt)
    {
        (pContext); // unused
        (pExt); // unused
        return TRUE;
    }

    // Used to uninitialize the class
    // function is stored in the handler map in user's code.
    static void UninitRequestHandlerClass()
    {
        return;
    }
};

struct CRequestStats
{
    long m_lTotalRequests;
    long m_lFailedRequests;
    __int64 m_liTotalResponseTime;
    long m_lAvgResponseTime;
    long m_lCurrWaiting;
    long m_lMaxWaiting;
    long m_lActiveThreads;

    CRequestStats() throw()
    {
        m_lTotalRequests = 0;
        m_lFailedRequests = 0;
        m_liTotalResponseTime = 0;
        m_lAvgResponseTime = 0;
        m_lCurrWaiting = 0;
        m_lMaxWaiting = 0;
        m_lActiveThreads = 0;
    }

    void RequestHandled(__in AtlServerRequest *pRequestInfo, __in BOOL bSuccess)
    {
        ATLENSURE(pRequestInfo);

        InterlockedIncrement(&m_lTotalRequests);
        if(!bSuccess)
            InterlockedIncrement(&m_lFailedRequests);

        long lTicks;

#ifndef ATL_NO_MMSYS
        lTicks = (long)(timeGetTime() - pRequestInfo->dwStartTicks);
#else
        lTicks = (long)(GetTickCount() - pRequestInfo->dwStartTicks);
#endif
        __int64 liTotalResponseTime = Add64(&m_liTotalResponseTime, lTicks);
        long lAv = (long)(liTotalResponseTime / m_lTotalRequests);
        InterlockedExchange(&m_lAvgResponseTime, lAv);

        InterlockedDecrement(&m_lActiveThreads);
    }

    long GetTotalRequests() throw()
    {
        return m_lTotalRequests;
    }

    long GetFailedRequests() throw()
    {
        return m_lFailedRequests;
    }

    long GetAvgResponseTime() throw()
    {
        return m_lAvgResponseTime;
    }

    void OnRequestReceived() throw()
    {
        long nCurrWaiting = InterlockedIncrement(&m_lCurrWaiting);
        AtlInterlockedUpdateMax(nCurrWaiting, &m_lMaxWaiting);
    }

    void OnRequestDequeued() throw()
    {
        InterlockedDecrement(&m_lCurrWaiting);
        InterlockedIncrement(&m_lActiveThreads);
    }

    long GetCurrWaiting() throw()
    {
        return m_lCurrWaiting;
    }

    long GetMaxWaiting() throw()
    {
        return m_lMaxWaiting;
    }

    long GetActiveThreads() throw()
    {
        return m_lActiveThreads;
    }

private:
    // not actually atomic, but it will add safely.

    // the returned value is not 100% guaranteed to be
    // correct, but should be correct more often than
    // just reading the __int64
    // the 2 cases where the return value will not be
    // a valid result value from an add are:
    // * multiple threads wrap the low part in rapid succession
    // * different threads are adding values with different signs

    // this is good enough for our use in RequestHandled -
    // we always add positive values and we shouldn't wrap 32-bits often
    inline __int64 Add64(__inout __int64* pn, long nAdd)
    {
        ATLENSURE(pn != NULL);

#if defined(_WIN64) && defined(_M_CEE)

        // Use System::Threading::Interlocked::Add because InterlockedExchangeAdd is an intrisinc not supported in managed code
        // with 64bits compilers.
        // Also, System::Threading::Interlocked::Add support 64bits operands.
        return System::Threading::Interlocked::Add(*pn, nAdd);

#else

        long* pnLow = (long*)(LPBYTE(pn) + offsetof(LARGE_INTEGER, LowPart));
        long* pnHigh = (long*)(LPBYTE(pn) + offsetof(LARGE_INTEGER, HighPart));

        long nOrigLow = InterlockedExchangeAdd(pnLow, nAdd);
        long nNewLow = nOrigLow + nAdd;
        long nNewHigh = *pnHigh;
        if(nAdd > 0 && nNewLow < nOrigLow)
            nNewHigh = InterlockedIncrement(pnHigh);
        else if(nAdd < 0 && nNewLow > nOrigLow)
            nNewHigh = InterlockedDecrement(pnHigh);

        LARGE_INTEGER li;
        li.LowPart = nNewLow;
        li.HighPart = nNewHigh;
        return li.QuadPart;

#endif
    }
};

class CStdRequestStats : public CRequestStats
{

public:
    HRESULT Initialize() throw()
    {
        return S_OK;
    }

    void Uninitialize() throw()
    {
    }
};

#define PERF_REQUEST_OBJECT 100

struct CPerfRequestStatObject : public CPerfObject,
    public CRequestStats
{
    DECLARE_PERF_CATEGORY_EX(PERF_REQUEST_OBJECT, IDS_PERFMON_REQUEST, IDS_PERFMON_REQUEST_HELP, PERF_DETAIL_NOVICE, 0, sizeof(CPerfRequestStatObject), MAX_PATH, -1);
    BEGIN_COUNTER_MAP(CPerfRequestStatObject)
    DEFINE_COUNTER(m_lTotalRequests, IDS_PERFMON_REQUEST_TOTAL, IDS_PERFMON_REQUEST_TOTAL_HELP, PERF_COUNTER_RAWCOUNT, -1)
    DEFINE_COUNTER(m_lFailedRequests, IDS_PERFMON_REQUEST_FAILED, IDS_PERFMON_REQUEST_FAILED_HELP, PERF_COUNTER_RAWCOUNT, -1)
    DEFINE_COUNTER(m_lTotalRequests, IDS_PERFMON_REQUEST_RATE, IDS_PERFMON_REQUEST_RATE_HELP, PERF_COUNTER_COUNTER, -1)
    DEFINE_COUNTER(m_lAvgResponseTime, IDS_PERFMON_REQUEST_AVG_RESPONSE_TIME, IDS_PERFMON_REQUEST_AVG_RESPONSE_TIME_HELP, PERF_COUNTER_RAWCOUNT, -1)
    DEFINE_COUNTER(m_lCurrWaiting, IDS_PERFMON_REQUEST_CURR_WAITING, IDS_PERFMON_REQUEST_CURR_WAITING_HELP, PERF_COUNTER_RAWCOUNT, -1)
    DEFINE_COUNTER(m_lMaxWaiting, IDS_PERFMON_REQUEST_MAX_WAITING, IDS_PERFMON_REQUEST_MAX_WAITING_HELP, PERF_COUNTER_RAWCOUNT, -1)
    DEFINE_COUNTER(m_lActiveThreads, IDS_PERFMON_REQUEST_ACTIVE_THREADS, IDS_PERFMON_REQUEST_ACTIVE_THREADS, PERF_COUNTER_RAWCOUNT, -1)
    END_COUNTER_MAP()
};

class CRequestPerfMon : public CPerfMon
{
public:
    BEGIN_PERF_MAP(_T("ATL Server:Request"))
    CHAIN_PERF_CATEGORY(CPerfRequestStatObject)
    END_PERF_MAP()
};

class CPerfMonRequestStats
{
    CRequestPerfMon m_PerfMon;
    CPerfRequestStatObject * m_pPerfObjectInstance;
    CPerfRequestStatObject * m_pPerfObjectTotal;

public:
    CPerfMonRequestStats() throw()
    {
        m_pPerfObjectInstance = NULL;
        m_pPerfObjectTotal = NULL;
    }

    HRESULT Initialize() throw()
    {
        HRESULT hr;

        m_pPerfObjectInstance = NULL;
        m_pPerfObjectTotal = NULL;

        hr = m_PerfMon.Initialize();
        if(SUCCEEDED(hr))
        {
            CPerfLock lock(&m_PerfMon);
            if(FAILED(hr = lock.GetStatus()))
            {
                return hr;
            }

            HINSTANCE hInst = _AtlBaseModule.GetModuleInstance();
            WCHAR szName[MAX_PATH];
            if(GetModuleFileNameW(hInst, szName, MAX_PATH) == 0)
            {
                return E_FAIL;
            }
            szName[MAX_PATH-1] = 0;

            hr = m_PerfMon.CreateInstanceByName(L"_Total", &m_pPerfObjectTotal);
            if(FAILED(hr))
            {
                return hr;
            }

            hr = m_PerfMon.CreateInstanceByName(szName, &m_pPerfObjectInstance);
            if(FAILED(hr))
            {
                m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);
                m_pPerfObjectTotal = NULL;
                return hr;
            }
        }

        return hr;
    }

    void Uninitialize() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            m_PerfMon.ReleaseInstance(m_pPerfObjectInstance);
        if(m_pPerfObjectTotal != NULL)
            m_PerfMon.ReleaseInstance(m_pPerfObjectTotal);

        m_pPerfObjectInstance = NULL;
        m_pPerfObjectTotal = NULL;

        m_PerfMon.UnInitialize();
    }

    void RequestHandled(__in AtlServerRequest *pRequestInfo, __in BOOL bSuccess) throw()
    {
        if(m_pPerfObjectInstance != NULL)
            m_pPerfObjectInstance->RequestHandled(pRequestInfo, bSuccess);
        if(m_pPerfObjectTotal != NULL)
            m_pPerfObjectTotal->RequestHandled(pRequestInfo, bSuccess);
    }

    long GetTotalRequests() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetTotalRequests();

        return 0;
    }

    long GetFailedRequests() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetFailedRequests();

        return 0;
    }

    long GetAvgResponseTime() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetAvgResponseTime();

        return 0;
    }

    void OnRequestReceived() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            m_pPerfObjectInstance->OnRequestReceived();
        if(m_pPerfObjectTotal != NULL)
            m_pPerfObjectTotal->OnRequestReceived();
    }

    void OnRequestDequeued() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            m_pPerfObjectInstance->OnRequestDequeued();
        if(m_pPerfObjectTotal != NULL)
            m_pPerfObjectTotal->OnRequestDequeued();
    }

    long GetCurrWaiting() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetCurrWaiting();

        return 0;
    }

    long GetMaxWaiting() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetMaxWaiting();

        return 0;
    }

    long GetActiveThreads() throw()
    {
        if(m_pPerfObjectInstance != NULL)
            return m_pPerfObjectInstance->GetActiveThreads();

        return 0;
    }
};

class CNoRequestStats
{
protected:

public:

    HRESULT Initialize() throw()
    {
        return S_OK;
    }

    void Uninitialize() throw()
    {
    }

    void RequestHandled(AtlServerRequest * /*pRequestInfo*/, BOOL /*bSuccess*/) throw()
    {
    }

    long GetTotalRequests() throw()
    {
        return 0;
    }

    long GetFailedRequests() throw()
    {
        return 0;
    }

    long GetAvgResponseTime() throw()
    {
        return 0;
    }

    void OnRequestReceived() throw()
    {
    }

    void OnRequestDequeued() throw()
    {
    }

    long GetCurrWaiting() throw()
    {
        return 0;
    }

    long GetMaxWaiting() throw()
    {
        return 0;
    }

    long GetActiveThreads() throw()
    {
        return 0;
    }
};

struct ATLServerDllInfo
{
    GETATLHANDLERBYNAME     pfnGetHandler;
    UNINITIALIZEATLHANDLERS pfnUninitHandlers;
    INITIALIZEATLHANDLERS pfnInitHandlers;
    IIsapiExtension *pExtension;
    IHttpServerContext *pContext;
};

class CDllCachePeer
{
public:
    struct DllInfo : public ATLServerDllInfo
    {
        DllInfo& operator=(__in const DllInfo& right) throw()
        {
            if(this != &right)
            {
                pfnGetHandler = right.pfnGetHandler;
                pfnUninitHandlers = right.pfnUninitHandlers;
                pfnInitHandlers = right.pfnInitHandlers;
                pExtension = right.pExtension;
                pContext = right.pContext;
            }
            return *this;
        }
    };

    BOOL Add(__in HINSTANCE hInst, __out DllInfo *pInfo) throw(...)
    {
        ATLENSURE(pInfo != NULL);
        pInfo->pfnInitHandlers = (INITIALIZEATLHANDLERS) GetProcAddress(hInst, ATLS_FUNCID_INITIALIZEHANDLERS);

        pInfo->pfnGetHandler = (GETATLHANDLERBYNAME) GetProcAddress(hInst, ATLS_FUNCID_GETATLHANDLERBYNAME);
        if(!pInfo->pfnGetHandler)
            return FALSE;

        pInfo->pfnUninitHandlers = (UNINITIALIZEATLHANDLERS) GetProcAddress(hInst, ATLS_FUNCID_UNINITIALIZEHANDLERS);

        if(pInfo->pfnInitHandlers)
        {
            pInfo->pfnInitHandlers(pInfo->pContext, pInfo->pExtension);
            pInfo->pContext = NULL; // won't be valid after this call
        }

        return TRUE;
    }

    void Remove(HINSTANCE /*hInst*/, __in DllInfo *pInfo) throw(...)
    {
        ATLENSURE(pInfo != NULL);
        if(pInfo->pfnUninitHandlers)
            (*pInfo->pfnUninitHandlers)();
    }

};

inline bool operator==(__in const CDllCachePeer::DllInfo& left, __in const CDllCachePeer::DllInfo& right) throw()
{
    return ((left.pfnGetHandler == right.pfnGetHandler) &&
            (left.pfnUninitHandlers == right.pfnUninitHandlers) &&
            (left.pfnInitHandlers == right.pfnInitHandlers) &&
            (left.pExtension == right.pExtension) &&
            (left.pContext == right.pContext)
           );
}



// Helper function to impersonate the client
// on the current thread
inline BOOL AtlImpersonateClient(__in IHttpServerContext *pServerContext)
{
    ATLENSURE(pServerContext);

    // impersonate the calling client on the current thread
    HANDLE hToken;
    _ATLTRY
    {
        if(!pServerContext->GetImpersonationToken(&hToken))
            return FALSE;
    }
    _ATLCATCHALL()
    {
        return FALSE;
    }

    if(!SetThreadToken(NULL, hToken))
        return FALSE;
    return TRUE;
}

// Helper class to set the thread impersonation token
// This is mainly used internally to ensure that we
// don't forget to revert to the process impersonation
// level
class CSetThreadToken
{
public:
    CSetThreadToken() : m_bShouldRevert(FALSE) {}

    BOOL Initialize(__in AtlServerRequest *pRequestInfo)
    {
        ATLENSURE(pRequestInfo);
        m_bShouldRevert = AtlImpersonateClient(pRequestInfo->pServerContext);
        return m_bShouldRevert;
    }

    ~CSetThreadToken() throw()
    {
        if(m_bShouldRevert && !RevertToSelf())
        {
            _AtlRaiseException((DWORD)EXCEPTION_ACCESS_VIOLATION);
        }
    }
protected:
    BOOL m_bShouldRevert;
};


// push_macro/pop_macro doesn't work in a template definition.
#pragma push_macro("new")
#undef new


//Base is the user's class that derives from CComObjectRoot and whatever
//interfaces the user wants to support on the object
template <class Base>
class _CComObjectHeapNoLock : public Base
{
public:
    typedef Base _BaseClass;
    HANDLE m_hHeap;

    _CComObjectHeapNoLock(void* = NULL, HANDLE hHeap = NULL)
    {
        m_hHeap = hHeap;
    }
    // Set refcount to -(LONG_MAX/2) to protect destruction and
    // also catch mismatched Release in debug builds
    ~_CComObjectHeapNoLock()
    {
        m_dwRef = -(LONG_MAX / 2);
        FinalRelease();
#ifdef _ATL_DEBUG_INTERFACES
        _AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
#endif
    }

    //If InternalAddRef or InternalRelease is undefined then your class
    //doesn't derive from CComObjectRoot
    STDMETHOD_(ULONG, AddRef)() throw()
    {
        return InternalAddRef();
    }
    STDMETHOD_(ULONG, Release)() throw()
    {
        ULONG l = InternalRelease();
        if(l == 0)
        {
            HANDLE hHeap = m_hHeap;;
            this->~_CComObjectHeapNoLock();
            if(hHeap != NULL)
            {
                HeapFree(hHeap, 0, this);
            }
        }
        return l;
    }
    //if _InternalQueryInterface is undefined then you forgot BEGIN_COM_MAP
    STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) throw()
    {
        return _InternalQueryInterface(iid, ppvObject);
    }

    static HRESULT WINAPI CreateInstance(_CComObjectHeapNoLock<Base>** pp, HANDLE hHeap) throw();
};



template <class Base>
HRESULT WINAPI _CComObjectHeapNoLock<Base>::CreateInstance(__deref_out _CComObjectHeapNoLock<Base>** pp, __in HANDLE hHeap) throw()
{
    ATLASSERT(pp != NULL);
    if(pp == NULL)
        return E_POINTER;
    *pp = NULL;

    HRESULT hRes = E_OUTOFMEMORY;
    // Allocate a fixed block size to avoid fragmentation
    void *pv = HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
                         __max(sizeof(AtlServerRequest), sizeof(_CComObjectHeapNoLock<CServerContext>)));
    if(pv == NULL)
    {
        return hRes;
    }
#pragma warning(push)
#pragma warning(disable: 6280)
    /* prefast noise VSW 493229 */
    _CComObjectHeapNoLock<Base>* p = new(pv) _CComObjectHeapNoLock<CServerContext>(NULL, hHeap);
#pragma warning(pop)

    p->SetVoid(NULL);
    p->InternalFinalConstructAddRef();
    hRes = p->_AtlInitialConstruct();
    if(SUCCEEDED(hRes))
        hRes = p->FinalConstruct();
    if(SUCCEEDED(hRes))
        hRes = p->_AtlFinalConstruct();
    p->InternalFinalConstructRelease();
    if(hRes != S_OK)
    {
        p->~_CComObjectHeapNoLock();
#pragma warning(push)
#pragma warning(disable: 6280)
        /* prefast noise VSW 493229 */
        HeapFree(hHeap, 0, p);
#pragma warning(pop)
        p = NULL;
    }
    *pp = p;
    return hRes;
}

inline CServerContext* CreateServerContext(__in HANDLE hRequestHeap) throw()
{
    _CComObjectHeapNoLock<CServerContext>* pContext;
    _CComObjectHeapNoLock<CServerContext>::CreateInstance(&pContext, hRequestHeap);
    return pContext;
}
#pragma pop_macro("new")

// _AtlGetHandlerName
// get handler name from stencil file. Ignore all server side comments
//  szFileName - the file from which to extract the handler name
//  szHandlerName - buffer into which handler name will be copied,
//       it is assumed to be of size MAX_PATH+ATL_MAX_HANDLER_NAME+2
inline HTTP_CODE _AtlGetHandlerName(__in LPCSTR szFileName, __out_ecount(MAX_PATH + ATL_MAX_HANDLER_NAME + 2) LPSTR szHandlerName)
{
    ATLASSERT(szFileName);
    ATLENSURE(szHandlerName);

    szHandlerName[0] = '\0';
    CAtlFile cfFile;
    HRESULT hr;

    _ATLTRY
    {
        hr = cfFile.Create(CA2CTEX<MAX_PATH>(szFileName), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
        if(FAILED(hr) || cfFile.m_h == NULL || GetFileType(cfFile.m_h) != FILE_TYPE_DISK)
        {
            if(hr == AtlHresultFromWin32(ERROR_FILE_NOT_FOUND))
                return HTTP_NOT_FOUND;
            else if(hr == AtlHresultFromWin32(ERROR_ACCESS_DENIED))
                return HTTP_UNAUTHORIZED;
            else
                return AtlsHttpError(500, IDS_ATLSRV_SERVER_ERROR_STENCILLOADFAIL);
        }
    }
    _ATLCATCHALL()
    {
        return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM); // CA2CTEX threw
    }

    HTTP_CODE hcErr = HTTP_SUCCESS;
    DWORD dwRead = 0;
    LPCSTR szHandler = "handler";
    LPCSTR pszHandlerPos = NULL;
    LPSTR pszHandlerName = szHandlerName;
    char szBuf[4097];
    LPSTR szCurly = NULL;
    LPSTR pszBuf = NULL;
    bool bInQuote = false;

    // state:
    //  0 = default/unknown
    //  1 = have "{"
    //  2 = have "{{" -- skip spaces
    //  3 = have "{{" -- check "handler"
    //  4 = have "handler" -- skip spaces
    //  5 = have "handler" -- get name
    //  6 = scan until first '}'
    //  7 = better be '}'
    //  8 = done
    int nState = 0;

    do
    {
        hr = cfFile.Read(szBuf, _countof(szBuf) - 1, dwRead);
        if(hr != S_OK)
        {
            return AtlsHttpError(500, ISE_SUBERR_READFILEFAIL); // failed reading
        }

        szBuf[dwRead] = '\0';
        pszBuf = szBuf;

        while(*pszBuf && nState != 8)
        {
            switch(nState)
            {
            case 0:
                //  0 = default/unknown

                // look for the first curly
                szCurly = strchr(pszBuf, '{');
                if(!szCurly)
                {
                    // skip to the end of the buffer
                    pszBuf = szBuf + dwRead - 1;
                }
                else
                {
                    pszBuf = szCurly;
                    nState = 1;
                }
                break;
            case 1:
                //  1 = have "{"
                if(*pszBuf == '{')
                {
                    nState = 2;
                }
                else
                {
                    nState = 0; // if the next character is not a '{', start over
                }
                break;
            case 2:
                if(!isspace(static_cast<unsigned char>(*pszBuf)))
                {
                    pszHandlerPos = szHandler;
                    pszBuf--;
                    nState = 3;
                }
                break;
            case 3:
                //  3 = partial handler "h..."
                if(*pszBuf != *pszHandlerPos)
                {
                    // not a handler, skip tag
                    nState = 6;
                }
                else
                {
                    pszHandlerPos++;
                    if(!*pszHandlerPos)  // at the end of the "handler" part
                        nState = 4;
                }
                break;
            case 4:
                //  4 = have "handler" -- skip spaces
                if(!isspace(static_cast<unsigned char>(*pszBuf)))
                {
                    if(*pszBuf == '\"')
                    {
                        bInQuote = true;
                    }
                    else
                    {
                        pszBuf--;
                    }
                    nState = 5;
                }
                break;
            case 5:
                //  5 = have "handler" -- get name
                if(isspace(static_cast<unsigned char>(*pszBuf)) && !bInQuote)
                {
                    if(*(pszHandlerName - 1) != '/')
                    {
                        // end of the name -- jump to getting the first '}'
                        nState = 6;
                    }
                    else
                    {
                        nState = 4;
                    }
                }
                else if(*pszBuf == '}')
                {
                    // end of the name -- jump to getting the second '}'
                    nState = 7;
                }
                else if(*pszBuf == '\"')
                {
                    if(bInQuote)
                    {
                        bInQuote = false;
                    }
                    else
                    {
                        hcErr = HTTP_FAIL;
                        nState = 8;
                    }
                }
                else
                {
                    // ensure we don't overwrite the buffer
                    if(pszHandlerName - szHandlerName >= MAX_PATH + ATL_MAX_HANDLER_NAME_LEN + 1)
                    {
                        hcErr =  HTTP_FAIL;
                        nState = 8;
                    }
                    else
                    {
                        *pszHandlerName++ = *pszBuf;
                    }
                }
                break;
            case 6:
                //  6 = scan until first '}'
                if(*pszBuf == '}')
                    nState = 7;
                break;
            case 7:
                //  7 = better be '}'
                if(*pszBuf != '}')
                {
                    hcErr = AtlsHttpError(500, ISE_SUBERR_BAD_HANDLER_TAG);
                    nState = 8;
                }
                if(*szHandlerName)
                    nState = 8;
                else
                    nState = 0;
                break;
            default:
                ATLASSERT(FALSE);
                return HTTP_INTERNAL_SERVER_ERROR;
            }

            pszBuf++;
        }
    }
    while(dwRead != 0 && nState != 8);

    *pszHandlerName = '\0';

    return hcErr;
}

// _AtlCrackHandler cracks a request path of the form dll_path/handler_name into its
// constituent parts
// szHandlerDllName - the full handler path of the form "dll_path/handler_name"
// szDllPath - the DLL path (should be of length MAX_PATH)
// szHandlerName - the handler name (should be of length ATL_MAX_HANDLER_NAME_LEN+1)
//
inline BOOL _AtlCrackHandler(
    __in_z LPCSTR szHandlerDllName,
    __out_ecount_part(*pdwszDllPathLen, *pdwszDllPathLen) LPSTR szDllPath,
    __inout LPDWORD pdwDllPathLen,
    __out_ecount_part(*pdwHandlerNameLen, *pdwHandlerNameLen) LPSTR szHandlerName,
    __inout LPDWORD pdwHandlerNameLen)
{
    ATLENSURE(szHandlerDllName != NULL);
    ATLASSERT(szDllPath != NULL);
    ATLENSURE(pdwDllPathLen != NULL);
    ATLASSERT(szHandlerName != NULL);
    ATLASSERT(pdwHandlerNameLen != NULL);

    BOOL bRet = TRUE;

    // skip leading spaces
    while(*szHandlerDllName && isspace(static_cast<unsigned char>(*szHandlerDllName)))
        ++szHandlerDllName;

    // get the handler name
    LPCSTR szSlash = strchr(szHandlerDllName, '/');
    LPCSTR szEnd = NULL;
    LPCSTR szSlashEnd = NULL;

    // if it is of the form <dll_name>/<handler_name>
    if(szSlash)
    {
        szEnd = szSlash;

        // skip trailing spaces on <dll_name>
        while(szEnd > szHandlerDllName && isspace(static_cast<unsigned char>(*(szEnd - 1))))
            --szEnd;

        szSlash++;
        // skip leading whitespace
        while(*szSlash && isspace(static_cast<unsigned char>(*szSlash)))
            szSlash++;

        // right trim szSlash;
        szSlashEnd = szSlash;
        while(*szSlashEnd && !isspace(static_cast<unsigned char>(*szSlashEnd)))
            szSlashEnd++;
    }
    else // only the <dll_name>
    {
        szSlash = ATL_HANDLER_NAME_DEFAULT;
        szSlashEnd = szSlash + sizeof(ATL_HANDLER_NAME_DEFAULT) - 1;

        // do it this way to handle paths with spaces
        // (e.g. "some path\subdirectory one\subdirectory two\dll_name.dll")
        szEnd = szHandlerDllName + strlen(szHandlerDllName);

        // skip trailing spaces
        while(szEnd > szHandlerDllName && isspace(static_cast<unsigned char>(*(szEnd - 1))))
            --szEnd;
    }

    // if the dll path is quoted, strip the quotes
    if(*szHandlerDllName == '\"' && *(szEnd - 1) == '\"' && szEnd > szHandlerDllName + 2)
    {
        szHandlerDllName++;
        szEnd--;
    }

    if(*pdwDllPathLen > (DWORD)(szEnd - szHandlerDllName) && (szEnd - szHandlerDllName >= 0))
    {
        Checked::memcpy_s(szDllPath, *pdwDllPathLen, szHandlerDllName, szEnd - szHandlerDllName);
        szDllPath[szEnd-szHandlerDllName] = '\0';
        *pdwDllPathLen = (DWORD)(szEnd - szHandlerDllName);
    }
    else
    {
        *pdwDllPathLen = (DWORD)(szEnd - szHandlerDllName) + 1;
        bRet = FALSE;
    }

    if(*pdwHandlerNameLen > (DWORD)(szSlashEnd - szSlash) && (szSlashEnd - szSlash >= 0))
    {
        Checked::memcpy_s(szHandlerName, *pdwHandlerNameLen, szSlash, (szSlashEnd - szSlash));
        szHandlerName[szSlashEnd-szSlash] = '\0';
        *pdwHandlerNameLen = (DWORD)(szSlashEnd - szSlash);
    }
    else
    {
        *pdwHandlerNameLen = (DWORD)(szSlashEnd - szSlash) + 1;
        bRet = FALSE;
    }

    return bRet;
}

inline __checkReturn __success(return == HTTP_SUCCESS) HTTP_CODE _AtlLoadRequestHandler(
    __in LPCSTR szDllPath,
    __in LPCSTR szHandlerName,
    __in IHttpServerContext *pServerContext,
    __out HINSTANCE *phInstance,
    __deref_out_opt IRequestHandler **ppHandler,
    __in IIsapiExtension *pExtension,
    __in IDllCache *pDllCache) throw(...)
{
    ATLENSURE(phInstance != NULL);
    ATLENSURE(ppHandler != NULL);
    ATLENSURE(pDllCache != NULL);
    *phInstance = NULL;
    *ppHandler = NULL;

    ATLServerDllInfo DllInfo;
    DllInfo.pExtension = pExtension;
    DllInfo.pContext = pServerContext;
    if(!IsFullPathA(szDllPath))
    {
        CHAR szFileName[MAX_PATH];
        if(!GetScriptFullFileName(szDllPath, szFileName, pServerContext))
        {
            return HTTP_FAIL;
        }

        *phInstance = pDllCache->Load(szFileName, (void *)&DllInfo);
    }
    else
    {
        *phInstance = pDllCache->Load(szDllPath, (void *)&DllInfo);
    }
    if(!*phInstance)
    {
        ATLTRACE("LoadLibrary failed: '%s' with error: %d\r\n", szDllPath, GetLastError());
        return AtlsHttpError(500, ISE_SUBERR_LOADLIB);
    }

    CComPtr<IUnknown> spUnk;

    if(!DllInfo.pfnGetHandler ||
       !DllInfo.pfnGetHandler(szHandlerName, pExtension, &spUnk) ||
       FAILED(spUnk->QueryInterface(ppHandler)))
    {
        pDllCache->Free(*phInstance);
        *phInstance = NULL;
        return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
    }

    return HTTP_SUCCESS;
} // _AtlLoadRequestHandler


class CTransferServerContext : public CComObjectRootEx<CComMultiThreadModel>,
    public CWrappedServerContext
{
public:
    char m_szFileName[MAX_PATH];
    char m_szQueryString[ATL_URL_MAX_PATH_LENGTH+1];
    CStringA m_strUrl;
    IWriteStream *m_pStream;

    BEGIN_COM_MAP(CTransferServerContext)
    COM_INTERFACE_ENTRY(IHttpServerContext)
    END_COM_MAP()

    CTransferServerContext() throw()
    {
        m_pStream = NULL;
    }

    BOOL Initialize(__in CTransferServerContext *pOtherContext)
    {
        ATLENSURE(pOtherContext != NULL);
        return Initialize(pOtherContext->m_strUrl, pOtherContext->m_pStream, pOtherContext->m_spParent);
    }

    BOOL Initialize(__in LPCSTR szUrl, __in IWriteStream *pStream, __in IHttpServerContext *pParent) throw()
    {
        m_pStream = pStream;
        m_spParent = pParent;

        _ATLTRY
        {
            m_strUrl = szUrl; // we store the URL in case we need to initialize another context from this context
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }

        long nUrlLen = m_strUrl.GetLength();
        m_szFileName[0] = '\0';

        if(!IsFullPathA(szUrl))
        {
            DWORD dwLen = MAX_PATH;
            BOOL bRet = TRUE;
            _ATLTRY
            {
                bRet = m_spParent->GetServerVariable(
                    "APPL_PHYSICAL_PATH",
                    m_szFileName,
                    &dwLen);
            }
            _ATLCATCHALL()
            {
                bRet = FALSE;
            }

            if(!bRet)
            {
                return bRet;
            }
        }

        // check for query params
        LPCSTR szMark = strchr(szUrl, '?');
        if(szMark)
        {
            size_t nPathLen = szMark - szUrl;
            size_t nLen;

            if((nPathLen >= 0) && (nPathLen < MAX_PATH))
            {
                nLen = strlen(m_szFileName) + nPathLen;
                if(nLen < MAX_PATH)
                {
#pragma warning(push)
#pragma warning(disable: 22008)
                    /* Prefast false warning about unbound nPathLen in the below fragment -
                       we already have necessary checks few lines above
                    */
                    if(m_szFileName[0])
                    {
                        Checked::strcat_s(m_szFileName, MAX_PATH - nLen, szUrl);
                    }
                    else
                    {
                        Checked::memcpy_s(m_szFileName, MAX_PATH, szUrl, nPathLen);
                        m_szFileName[nPathLen] = '\0';
                    }
#pragma warning(pop)
                }
                else
                {
                    return FALSE; // path would overwrite buffer
                }
            }
            else
            {
                return FALSE; // path would overwrite buffer
            }

            // save query params
            nLen = strlen(szMark + 1);
            if(nLen < ATL_URL_MAX_PATH_LENGTH)
            {
                Checked::strcpy_s(m_szQueryString, ATL_URL_MAX_PATH_LENGTH, szMark + 1);
            }
            else
            {
                return FALSE; // url would overwrite buffer
            }
        }
        else
        {
            // no query string
            size_t nLen = strlen(m_szFileName) + nUrlLen;
            if(nLen < MAX_PATH)
            {
                if(m_szFileName[0])
                {
                    Checked::strcat_s(m_szFileName, MAX_PATH - nLen, szUrl);
                }
                else
                {
                    Checked::strcpy_s(m_szFileName, MAX_PATH, szUrl);
                }
            }
            else
            {
                return FALSE; // path would be too long
            }
            m_szQueryString[0] = '\0';
        }

        return TRUE;
    }
    BOOL WriteClient(__in_bcount(*pdwBytes) void *pvBuffer, __inout DWORD *pdwBytes)
    {
        ATLENSURE(m_pStream != NULL);

        HRESULT hr = S_OK;
        _ATLTRY
        {
            m_pStream->WriteStream((LPCSTR) pvBuffer, *pdwBytes, pdwBytes);
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }

        return SUCCEEDED(hr);
    }

    LPCSTR GetQueryString()
    {
        ATLASSUME(m_spParent);
        return m_szQueryString;
    }

    LPCSTR GetScriptPathTranslated()
    {
        ATLASSUME(m_spParent);
        return m_szFileName;
    }

    LPCSTR GetPathTranslated()
    {
        ATLASSUME(m_spParent);
        return m_szFileName;
    }

    // Asynchronous writes will not work properly in a child handler
    BOOL AsyncWriteClient(void * /*pvBuffer*/, DWORD * /*pdwBytes*/)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }

    // These next few methods are to protect against attempting to parse form data twice
    // We tell the new handler that it was a GET request
    LPCSTR GetRequestMethod()
    {
        ATLASSUME(m_spParent);
        return "GET";
    }

    // The handler should not query these methods -- they are only useful if attempting to
    // parse form data, which is not allowed in child handlers.
    BOOL ReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        return FALSE;
    }

    BOOL AsyncReadClient(void * /*pvBuffer*/, DWORD * /*pdwSize*/)
    {
        return FALSE;
    }

    DWORD GetTotalBytes()
    {
        ATLASSERT(FALSE);
        return 0;
    }

    DWORD GetAvailableBytes()
    {
        ATLASSERT(FALSE);
        return 0;
    }

    BYTE *GetAvailableData()
    {
        ATLASSERT(FALSE);
        return NULL;
    }

    LPCSTR GetContentType()
    {
        ATLASSERT(FALSE);
        return NULL;
    }
};

class CAllocContextBase
{
public:
    virtual HTTP_CODE Alloc(IHttpServerContext **ppNewContext) = 0;
};

ATL_NOINLINE inline HTTP_CODE _AtlRenderInclude(
    __in IHttpServerContext *pServerContextNew,
    __in LPCSTR szFileName,
    __in LPCSTR szQueryParams,
    __in WORD wCodePage,
    __in CAllocContextBase *pAllocContext,
    __in IServiceProvider *pServiceProvider,
    __in_opt IHttpRequestLookup *pLookup,
    __inout_opt CStencilState* pState = NULL)
{
    ATLENSURE(pServiceProvider != NULL);
    AtlServerRequest* pRequestInfo = NULL;
    HTTP_CODE hcErr = HTTP_SUCCESS;

    // get a pointer to the ISAPI extension
    CComPtr<IIsapiExtension> spExtension;
    if(S_OK != pServiceProvider->QueryInterface(&spExtension) ||
       !spExtension)
    {
        return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
    }

    // get a pointer to the extension's dll cache
    CComPtr<IDllCache> spDllCache;
    if(S_OK != pServiceProvider->QueryService(__uuidof(IDllCache),
            __uuidof(IDllCache),
            (void**)&spDllCache) ||
       !spDllCache)
    {
        return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
    }

#ifdef _DEBUG
    bool bAsyncAllowed = false;
#endif
    if(pState && pState->pIncludeInfo)
    {
        pRequestInfo = pState->pIncludeInfo;
        pState->pIncludeInfo = NULL;
#ifdef _DEBUG
        bAsyncAllowed = true;
#endif
    }
    else
    {
        ATLASSERT(spDllCache);
        ATLASSERT(spExtension);

        pRequestInfo = spExtension->CreateRequest();
        if(pRequestInfo == NULL)
        {
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        }

        pRequestInfo->dwRequestState = ATLSRV_STATE_BEGIN;
        pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
        pRequestInfo->pDllCache = spDllCache;
        pRequestInfo->pExtension = spExtension;
        pRequestInfo->pServerContext = pServerContextNew;
        if(pState && pState->pParentInfo)
        {
            pRequestInfo->pUserData = pState->pParentInfo->pUserData;
        }

        // Extract the file extension of the included file by searching
        // for the first '.' from the right.
        // Can't use _tcsrchr because we have to use the stencil's codepage
        LPCSTR szDot = NULL;
        LPCSTR szMark = szFileName;
        ATLENSURE(szMark != NULL);
        while(*szMark)
        {
            if(*szMark == '.')
                szDot = szMark;

            LPCSTR szNext = CharNextExA(wCodePage, szMark, 0);
            if(szNext == szMark)
            {
                // embedded null
                pRequestInfo->pServerContext = NULL;
                spExtension->FreeRequest(pRequestInfo);
                return HTTP_FAIL;
            }
            szMark = szNext;
        }

        if(szDot && AsciiStricmp(szDot, c_AtlSRFExtension) == 0)
        {
            hcErr = spExtension->LoadDispatchFile(szFileName, pRequestInfo);
            if(hcErr)
            {
                pRequestInfo->pServerContext = NULL;
                spExtension->FreeRequest(pRequestInfo);
                return hcErr;
            }
        }
        else if(szDot && AsciiStricmp(szDot, c_AtlDLLExtension) == 0)
        {
            // Get the handler name if they used the asdf.dll?Handler=Default notation
            char szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1] = { '\0' };

            LPCSTR szStart = strstr(szQueryParams, "Handler=");
            if(szStart &&
               ((szStart == szQueryParams) ||
                ((szStart > szQueryParams) && (*(szStart - 1) == '&'))))
            {
                szStart += 8;  // Skip past "Handler" and the "="
                LPCSTR szEnd = strchr(szStart, '&');
                if(szEnd)
                {
                    Checked::memcpy_s(szHandlerName, ATL_MAX_HANDLER_NAME_LEN + 1, szStart, __min((szEnd - szStart), ATL_MAX_HANDLER_NAME_LEN));
                    szHandlerName[__min((szEnd-szStart), ATL_MAX_HANDLER_NAME_LEN)] = '\0';
                }
                else
                {
                    if(!SafeStringCopy(szHandlerName, szStart))
                    {
                        // handler name too long
                        pRequestInfo->pServerContext = NULL;
                        spExtension->FreeRequest(pRequestInfo);
                        return HTTP_FAIL;
                    }
                }
            }
            else
            {
                ATLASSERT(ATL_MAX_HANDLER_NAME_LEN >= sizeof(ATL_HANDLER_NAME_DEFAULT));
                Checked::memcpy_s(szHandlerName, ATL_MAX_HANDLER_NAME_LEN + 1, ATL_HANDLER_NAME_DEFAULT, sizeof(ATL_HANDLER_NAME_DEFAULT));
            }

            pRequestInfo->dwRequestType = ATLSRV_REQUEST_DLL;

            hcErr = spExtension->LoadRequestHandler(szFileName, szHandlerName, pRequestInfo->pServerContext,
                                                    &pRequestInfo->hInstDll, &pRequestInfo->pHandler);
            if(hcErr)
            {
                pRequestInfo->pServerContext = NULL;
                spExtension->FreeRequest(pRequestInfo);
                return hcErr;
            }
        }
        else
        {
            // unknown extension
            pRequestInfo->pServerContext = NULL;
            spExtension->FreeRequest(pRequestInfo);
            return HTTP_ERROR(500, ISE_SUBERR_UNEXPECTED);
        }

        DWORD dwStatus;
        hcErr = pRequestInfo->pHandler->GetFlags(&dwStatus);

        if(hcErr)
        {
            pRequestInfo->pHandler->UninitializeHandler();
            pRequestInfo->pServerContext = NULL;
            spExtension->FreeRequest(pRequestInfo);
            return hcErr;
        }

        if(dwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
        {
#ifdef _DEBUG
            bAsyncAllowed = true;
#endif
            ATLENSURE(pAllocContext != NULL);
            hcErr = pAllocContext->Alloc(&pRequestInfo->pServerContext);
            if(hcErr)
            {
                pRequestInfo->pHandler->UninitializeHandler();
                if(pRequestInfo->pServerContext == pServerContextNew)
                {
                    pRequestInfo->pServerContext = NULL;
                }
                spExtension->FreeRequest(pRequestInfo);
                return hcErr;
            }
        }

        hcErr = pRequestInfo->pHandler->InitializeChild(pRequestInfo, pServiceProvider, pLookup);
        if(hcErr)
        {
            pRequestInfo->pHandler->UninitializeHandler();
            if(pRequestInfo->pServerContext == pServerContextNew)
            {
                pRequestInfo->pServerContext = NULL;
            }
            spExtension->FreeRequest(pRequestInfo);
            return hcErr;
        }

        pRequestInfo->pfnHandleRequest = &IRequestHandler::HandleRequest;
    }

    if(pRequestInfo)
    {
        if(!hcErr)
        {
            ATLASSERT(pRequestInfo->pfnHandleRequest != NULL);
            hcErr = (pRequestInfo->pHandler->*pRequestInfo->pfnHandleRequest)(pRequestInfo, pServiceProvider);

#ifdef _DEBUG
            // must use ATLSRV_INIT_USEASYNC to use ASYNC returns
            if(IsAsyncStatus(hcErr))
            {
                ATLASSERT(bAsyncAllowed);
            }
#endif

            if(IsAsyncStatus(hcErr))
            {
                ATLASSERT(pState); // state is required for async
                if(IsAsyncContinueStatus(hcErr))
                {
                    pState->pIncludeInfo = pRequestInfo;
                    pRequestInfo->dwRequestState = ATLSRV_STATE_CONTINUE;
                }
                else if(IsAsyncDoneStatus(hcErr))
                {
                    pRequestInfo->pHandler->UninitializeHandler();
                    if(pRequestInfo->pServerContext == pServerContextNew)
                    {
                        pRequestInfo->pServerContext = NULL;
                    }
                    spExtension->FreeRequest(pRequestInfo);
                }
            }
            else
            {
                pRequestInfo->pHandler->UninitializeHandler();
                if(pRequestInfo->pServerContext == pServerContextNew)
                {
                    pRequestInfo->pServerContext = NULL;
                }
                spExtension->FreeRequest(pRequestInfo);
            }
        }
    }
    else
    {
        hcErr = AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
    }

    return hcErr;
}

// CAllocTransferAsyncContext is an unsupported implementation detail, used
// for implementing _AtlTransferRequest.
class CAllocTransferAsyncContext :
    public CAllocContextBase
{
public:
    CAllocTransferAsyncContext(CTransferServerContext *pInitialContext):
        m_pInitialContext(pInitialContext)
    {
    }

    HTTP_CODE Alloc(IHttpServerContext** ppNewContext)
    {
        if(!ppNewContext)
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
        *ppNewContext = NULL;

        CComObjectNoLock<CTransferServerContext>* pServerContext = NULL;
        ATLTRY(pServerContext = new CComObjectNoLock<CTransferServerContext>);
        if(pServerContext == NULL)
            return AtlsHttpError(500, ISE_SUBERR_OUTOFMEM);
        pServerContext->Initialize(m_pInitialContext);
        pServerContext->AddRef();
        *ppNewContext  = pServerContext;
        return HTTP_SUCCESS;
    }
private:
    CTransferServerContext *m_pInitialContext;
};

inline HTTP_CODE _AtlTransferRequest(
    __in AtlServerRequest *pRequest,
    __in IServiceProvider *pServiceProvider,
    __in IWriteStream *pWriteStream,
    __in_opt IHttpRequestLookup *pLookup,
    __in LPCSTR szNewUrl,
    __in WORD nCodePage,
    __in bool bContinueAfterProcess,
    __inout_opt CStencilState *pState)
{
    CComObjectStackEx<CTransferServerContext> serverContext;
    if(!serverContext.Initialize(szNewUrl, pWriteStream, pRequest->pServerContext))
        return AtlsHttpError(500, 0);
    CAllocTransferAsyncContext AsyncAllocObj(&serverContext);
    HTTP_CODE hcErr = _AtlRenderInclude(static_cast<IHttpServerContext*>(&serverContext),
                                        serverContext.m_szFileName,
                                        serverContext.m_szQueryString,
                                        nCodePage,
                                        &AsyncAllocObj,
                                        pServiceProvider,
                                        pLookup,
                                        pState);
    if(hcErr == HTTP_SUCCESS && bContinueAfterProcess)
        return hcErr;
    return HTTP_SUCCESS_NO_PROCESS;
}

//
// This function is now deprecated. ATL is not using it anymore.
//
inline ATL_DEPRECATED("Do not use this function.")
void __cdecl AtlsSecErrHandlerFunc(int /* nCode */, void * /* pv */)
{
    //
    // a buffer overflow has occurred in your code
    //
    ATLASSERT(FALSE);

    //
    // terminate process (safest thing to do)
    //
    TerminateProcess(GetCurrentProcess(), 1);
}

//
// Class CIsapiExtension
// The main ISAPI Extension implementation.
// Template parameters
// ThreadPoolClass: Specifies the thread pool that will be used by the
//		extension to queue incoming requests. CThreadPool is the
//		default and is declared and implemented in ATLUTIL.H. This class
//		templatizes on a worker thread class. The worker thread class
//		represents an abstraction of a thread that will be used to
//		process requests as they are dequeued from the pool's work queue.
//		You would change this parameter if you wanted to use a completely
//		different thread pool, or, more commonly, if you wanted to use
//		a different worker thread class. Request processing code can
//		access a pointer to the worker thread class, which allows the
//		request handling code to easily access per-thread data.
// CRequestStatClass:	Specifies the class to be used to track request statistics
//		CNoRequestStats is the default which is a noop class.
//		You would change this parameter to provide a class that will
//		track request statistics for you. ATL provides CStdRequestStats
//		and CPerfRequestStatObject but these classes should be used
//		with caution because they require interlocked operations to
//		keep track of request statistics which can affect server performance.
// HttpUserErrorTextProvider: This class provides error text messages
//		and headers, including  resource IDs of error messages to the
//		isapi extension's error handling functions. You would change this
//		parameter if you wanted to provide your own error headers and/or
//		messages in response to error encountered during request processing.
// CPageCacheStats, CStencilCacheStats: These two classes are used to keep
//		statistics about the page and stencil caches. You could change these
//		paramters if you wanted to track statistics for these caches. ATL
//		provides CPerfStatClass and CStdStatClass to store the stat data but
//		using these classes can affect server performance because they use
//		interlocked operations internally to store the data.
template <  class ThreadPoolClass = CThreadPool<CIsapiWorker>,
         class CRequestStatClass = CNoRequestStats,
         class HttpUserErrorTextProvider = CDefaultErrorProvider,
         class WorkerThreadTraits = DefaultThreadTraits,
         class CPageCacheStats = CNoStatClass,
         class CStencilCacheStats = CNoStatClass >
class CIsapiExtension :
    public IServiceProvider, public IIsapiExtension, public IRequestStats
{
private:

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

    DWORD m_dwCriticalIsapiError;

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

protected:
    DWORD m_dwTlsIndex;

    typedef CWorkerThread<WorkerThreadTraits> extWorkerType;

    extWorkerType m_WorkerThread;
    ThreadPoolClass m_ThreadPool;

    CDllCache<extWorkerType, CDllCachePeer> m_DllCache;
    CFileCache<extWorkerType, CPageCacheStats, CPageCachePeer> m_PageCache;
    CComObjectGlobal<CStencilCache<extWorkerType, CStencilCacheStats > > m_StencilCache;
    HttpUserErrorTextProvider m_UserErrorProvider;
    HANDLE m_hRequestHeap;
    CComCriticalSection m_critSec;

    // Dynamic services stuff
    struct ServiceNode
    {
        HINSTANCE hInst;
        IUnknown *punk;
        GUID guidService;
        IID riid;

        ServiceNode() throw()
        {
        }

        ServiceNode(const ServiceNode& that) throw()
            : hInst(that.hInst), punk(that.punk), guidService(that.guidService), riid(that.riid)
        {
        }
    };

    class CServiceEqualHelper
    {
    public:
        static bool IsEqual(__in const ServiceNode& t1, __in const ServiceNode& t2) throw()
        {
            return (InlineIsEqualGUID(t1.guidService, t2.guidService) != 0 &&
                    InlineIsEqualGUID(t1.riid, t2.riid) != 0);
        }
    };

    CSimpleArray<ServiceNode, CServiceEqualHelper> m_serviceMap;

public:
    CWin32Heap m_heap;

    CRequestStatClass m_reqStats;

    AtlServerRequest *CreateRequest()
    {
        // Allocate a fixed block size to avoid fragmentation
        AtlServerRequest *pRequest = (AtlServerRequest *) HeapAlloc(m_hRequestHeap,
                                     HEAP_ZERO_MEMORY, __max(sizeof(AtlServerRequest), sizeof(_CComObjectHeapNoLock<CServerContext>)));
        if(!pRequest)
            return NULL;
        pRequest->cbSize = sizeof(AtlServerRequest);

        return pRequest;
    }

    void FreeRequest(__inout AtlServerRequest *pRequest)
    {
        _ReleaseAtlServerRequest(pRequest);
        HeapFree(m_hRequestHeap, 0, pRequest);
    }

    CIsapiExtension() throw()
    {
        m_hRequestHeap = NULL;
#ifdef _DEBUG
        m_bDebug = FALSE;
#endif

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

        m_dwCriticalIsapiError = 0;

#endif // ATL_NO_CRITICAL_ISAPI_ERROR
    }

    HTTP_CODE TransferRequest(
        __in AtlServerRequest *pRequest,
        __in IServiceProvider *pServiceProvider,
        __in IWriteStream *pWriteStream,
        __in_opt IHttpRequestLookup *pLookup,
        __in LPCSTR szNewUrl,
        __in WORD nCodePage,
        __in bool bContinueAfterProcess,
        __inout_opt CStencilState *pState)
    {
        HTTP_CODE hcErr;
        _ATLTRY
        {
            hcErr = _AtlTransferRequest(pRequest, pServiceProvider, pWriteStream,
            pLookup, szNewUrl, nCodePage, bContinueAfterProcess, pState);
        }
        _ATLCATCHALL()
        {
            hcErr = HTTP_FAIL;
        }
        return hcErr;
    }

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

    DWORD ReturnCriticalError(__in EXTENSION_CONTROL_BLOCK *pECB)
    {

        _ATLTRY
        {
            ATLENSURE(pECB);
            UINT uResId = 0;
            LPCSTR szHeader = NULL;

            CStringA strStatus;
            CStringA strBody;
            CStringA strFormat;
            CStringA strError;

            DWORD dwErr = GetCriticalIsapiError();
            if(!strError.LoadString(dwErr))
            {
                strError.Format("Unknown Error %d", dwErr);
            }

#ifdef ATL_CRITICAL_ISAPI_ERROR_LOGONLY
            // we've logged the real error - don't send detailed internal info to the user
            m_UserErrorProvider.GetErrorText(500,
            SUBERR_NONE,
            &szHeader,
            &uResId);

            if(!uResId || !strBody.LoadString(uResId))
            {
                strBody = "<html><body>A server error has occurred.</body></html>";
            }
#else
            m_UserErrorProvider.GetErrorText(500,
            ISE_SUBERR_ISAPISTARTUPFAILED,
            &szHeader,
            &uResId);
            if(!uResId || !strFormat.LoadString(uResId))
            {
                strFormat = "<html><body>A critical error has occurred initializing this ISAPI extension: %s</body></html>";
            }
            strBody.Format(strFormat, strError);
#endif
            strStatus.Format("500 %s", szHeader);

            HSE_SEND_HEADER_EX_INFO hex;
            hex.pszStatus = (LPCSTR)strStatus;
            hex.pszHeader = NULL;
            hex.cchStatus = (DWORD)strStatus.GetLength();
            hex.cchHeader = 0;
            hex.fKeepConn = FALSE;

            pECB->ServerSupportFunction(pECB->ConnID,
            HSE_REQ_SEND_RESPONSE_HEADER_EX,
            &hex,
            NULL,
            NULL);

            DWORD dwBodyLen = strBody.GetLength();
            pECB->WriteClient(pECB->ConnID,
            (void *)(LPCSTR) strBody,
            &dwBodyLen,
            NULL);
        }
        _ATLCATCHALL()
        {
            return HSE_STATUS_ERROR;
        }
        return HSE_STATUS_SUCCESS;
    }

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

    DWORD HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) throw()
    {

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

        if(GetCriticalIsapiError() != 0)
        {
            return ReturnCriticalError(lpECB);
        }

#endif // ATL_NO_CRITICAL_ISAPI_ERROR

        AtlServerRequest *pRequestInfo = NULL;

        _ATLTRY
        {
            pRequestInfo = CreateRequest();
            if(pRequestInfo == NULL)
                return HSE_STATUS_ERROR;

            CServerContext *pServerContext = NULL;
            ATLTRY(pServerContext = CreateServerContext(m_hRequestHeap));
            if(pServerContext == NULL)
            {
                FreeRequest(pRequestInfo);
                return HSE_STATUS_ERROR;
            }

            pServerContext->Initialize(lpECB);
            pServerContext->AddRef();

            pRequestInfo->pServerContext = pServerContext;
            pRequestInfo->dwRequestType = ATLSRV_REQUEST_UNKNOWN;
            pRequestInfo->dwRequestState = ATLSRV_STATE_BEGIN;
            pRequestInfo->pExtension = static_cast<IIsapiExtension *>(this);
            pRequestInfo->pDllCache = static_cast<IDllCache *>(&m_DllCache);
#ifndef ATL_NO_MMSYS
            pRequestInfo->dwStartTicks = timeGetTime();
#else
            pRequestInfo->dwStartTicks = GetTickCount();
#endif
            pRequestInfo->pECB = lpECB;

            m_reqStats.OnRequestReceived();

            if(m_ThreadPool.QueueRequest(pRequestInfo))
                return HSE_STATUS_PENDING;

            if(pRequestInfo != NULL)
            {
                FreeRequest(pRequestInfo);
            }

        }
        _ATLCATCHALL()
        {
        }



        return HSE_STATUS_ERROR;
    }


    BOOL QueueRequest(__in AtlServerRequest * pRequestInfo)
    {
        return m_ThreadPool.QueueRequest(pRequestInfo);
    }

    CIsapiWorker *GetThreadWorker()
    {
        return (CIsapiWorker *) TlsGetValue(m_dwTlsIndex);
    }

    BOOL SetThreadWorker(__in CIsapiWorker *pWorker)
    {
        return TlsSetValue(m_dwTlsIndex, (void*)pWorker);
    }

    // Configuration functions -- override in base class if another value is desired
    virtual LPCSTR GetExtensionDesc() throw()
    {
        return "VC Server Classes";
    }
    virtual int GetNumPoolThreads() throw()
    {
        return 0;
    }
    virtual int GetPoolStackSize() throw()
    {
        return 0;
    }
    virtual HANDLE GetIOCompletionHandle() throw()
    {
        return INVALID_HANDLE_VALUE;
    }
    virtual DWORD GetDllCacheTimeout() throw()
    {
        return ATL_DLL_CACHE_TIMEOUT;
    }
    virtual DWORD GetStencilCacheTimeout() throw()
    {
        return ATL_STENCIL_CACHE_TIMEOUT;
    }
    virtual LONGLONG GetStencilLifespan() throw()
    {
        return ATL_STENCIL_LIFESPAN;
    }

    BOOL OnThreadAttach()
    {
        return SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
    }

    void OnThreadTerminate()
    {
        CoUninitialize();
    }

#ifndef ATL_NO_CRITICAL_ISAPI_ERROR

    BOOL SetCriticalIsapiError(__in DWORD dwErr = 1) throw()
    {
        m_dwCriticalIsapiError = dwErr;

        // send the error to the event log
        _ATLTRY
        {
            CStringA strBody;
            CStringA strFormat;
            CStringA strError;

            // format an error message
            if(!strError.LoadString(dwErr))
            {
                strError.Format("Unknown Error %d", dwErr);
            }

            if(!strFormat.LoadString(IDS_ATLSRV_CRITICAL_LOGMESSAGE))
            {
                strFormat = "A critical error has occurred initializing the ISAPI extension: %s";
            }
            strBody.Format(strFormat, strError);

            // take the base module name as the app name
            CPath path;
            {
                CStrBuf buf(path, MAX_PATH);
                DWORD dwLen = ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), buf, MAX_PATH);
                if(dwLen == MAX_PATH)
                    buf.SetLength(MAX_PATH);
            }
            path.StripPath();

            // log the event
            HANDLE h = RegisterEventSource(NULL, path);
            if(h)
            {
                LPCSTR szBody = strBody;
                ReportEventA(h, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &szBody, NULL);
                DeregisterEventSource(h);
            }
        }
        _ATLCATCHALL()
        {
        }

        return TRUE;
    }

    DWORD GetCriticalIsapiError() throw()
    {
        return m_dwCriticalIsapiError;
    }

#else

    BOOL SetCriticalIsapiError(__in DWORD dwErr = 1) throw()
    {
        dwErr; // not used
        return FALSE;
    }

    DWORD GetCriticalIsapiError() throw()
    {
        return 0;
    }

#endif // ATL_NO_CRITICAL_ISAPI_ERROR


    BOOL GetExtensionVersion(__out HSE_VERSION_INFO* pVer) throw()
    {
        ATLASSERT(pVer != NULL);
        if(pVer == NULL)
        {
            return FALSE;
        }
        // allocate a Tls slot for storing per thread data
        m_dwTlsIndex = TlsAlloc();

        // create a private heap for request data
        // this heap has to be thread safe to allow for
        // async processing of requests
        m_hRequestHeap = HeapCreate(0, 0, 0);
        if(!m_hRequestHeap)
        {
            ATLTRACE(atlTraceISAPI, 0, _T("Failed creating request heap.  Using process heap\n"));
            m_hRequestHeap = GetProcessHeap();
            if(!m_hRequestHeap)
            {
                return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_HEAPCREATEFAILED);
            }

        }

        // create a private heap (synchronized) for
        // allocations.  This reduces fragmentation overhead
        // as opposed to the process heap
        HANDLE hHeap = HeapCreate(0, 0, 0);
        if(!hHeap)
        {
            ATLTRACE(atlTraceISAPI, 0, _T("Failed creating extension heap.  Using process heap\n"));
            hHeap = GetProcessHeap();
            m_heap.Attach(hHeap, false);
        }
        else
        {
            m_heap.Attach(hHeap, true);
        }
        hHeap = NULL;

        if(S_OK != m_reqStats.Initialize())
        {
            ATLTRACE(atlTraceISAPI,
                     0,
                     _T("Initialization failed for request statistics perfmon support.\n")
                     _T("Check request statistics perfmon dll registration\n"));
        }

        if(S_OK != m_WorkerThread.Initialize())
        {
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_WORKERINITFAILED);
        }

        if(m_critSec.Init() != S_OK)
        {
            HRESULT hrIgnore = m_WorkerThread.Shutdown();
            (hrIgnore);
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_CRITSECINITFAILED);
        }

        if(S_OK != m_ThreadPool.Initialize(static_cast<IIsapiExtension*>(this), GetNumPoolThreads(), GetPoolStackSize(), GetIOCompletionHandle()))
        {
            HRESULT hrIgnore = m_WorkerThread.Shutdown();
            (hrIgnore);
            m_critSec.Term();
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_THREADPOOLFAILED);
        }

        if(FAILED(m_DllCache.Initialize(&m_WorkerThread, GetDllCacheTimeout())))
        {
            HRESULT hrIgnore = m_WorkerThread.Shutdown();
            (hrIgnore);
            m_ThreadPool.Shutdown();
            m_critSec.Term();
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_DLLCACHEFAILED);
        }

        if(FAILED(m_PageCache.Initialize(&m_WorkerThread)))
        {
            HRESULT hrIgnore = m_WorkerThread.Shutdown();
            (hrIgnore);
            m_ThreadPool.Shutdown();
            m_DllCache.Uninitialize();
            m_critSec.Term();
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_PAGECACHEFAILED);
        }

        if(S_OK != m_StencilCache.Initialize(static_cast<IServiceProvider*>(this),
                                             &m_WorkerThread,
                                             GetStencilCacheTimeout(),
                                             GetStencilLifespan()))
        {
            HRESULT hrIgnore = m_WorkerThread.Shutdown();
            (hrIgnore);
            m_ThreadPool.Shutdown();
            m_DllCache.Uninitialize();
            m_PageCache.Uninitialize();
            m_critSec.Term();
            return SetCriticalIsapiError(IDS_ATLSRV_CRITICAL_STENCILCACHEFAILED);
        }

        pVer->dwExtensionVersion = HSE_VERSION;
        Checked::strncpy_s(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, GetExtensionDesc(), _TRUNCATE);
        pVer->lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN - 1] = '\0';

        return TRUE;
    }

    BOOL TerminateExtension(DWORD /*dwFlags*/) throw()
    {
        m_critSec.Lock();
        for(int i = 0; i < m_serviceMap.GetSize(); i++)
        {
            ATLASSUME(m_serviceMap[i].punk != NULL);
            if(m_serviceMap[i].punk != NULL)
            {
                m_serviceMap[i].punk->Release();
            }
            m_DllCache.ReleaseModule(m_serviceMap[i].hInst);
        }
        m_critSec.Unlock();

        m_ThreadPool.Shutdown();
        m_StencilCache.Uninitialize();
        m_DllCache.Uninitialize();
        m_PageCache.Uninitialize();
        HRESULT hrShutdown = m_WorkerThread.Shutdown();
        m_reqStats.Uninitialize();
        m_critSec.Term();

        // free the request heap
        if(m_hRequestHeap != GetProcessHeap())
            HeapDestroy(m_hRequestHeap);

        // free the Tls slot that we allocated
        TlsFree(m_dwTlsIndex);

        return SUCCEEDED(hrShutdown);
    }

    static void WINAPI AsyncCallback(LPEXTENSION_CONTROL_BLOCK /*lpECB*/,
                                     __in PVOID pContext,
                                     __in DWORD cbIO,
                                     __in DWORD dwError) throw(...)
    {
        AtlServerRequest *pRequestInfo = reinterpret_cast<AtlServerRequest*>(pContext);
        ATLENSURE(pRequestInfo);
        if(pRequestInfo->m_hMutex)
        {
            // synchronize in case the previous async_noflush call isn't finished
            // setting up state for the next call.
            DWORD dwStatus = WaitForSingleObject(pRequestInfo->m_hMutex, ATLS_ASYNC_MUTEX_TIMEOUT);
            if(dwStatus != WAIT_OBJECT_0 && dwStatus != WAIT_ABANDONED)
            {
                _ATLTRY
                {
                    pRequestInfo->pExtension->RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
                }
                _ATLCATCHALL()
                {
                    ATLTRACE(_T("Warning: Uncaught user exception thrown and caught in AsyncCallback.\n"));
                    _ATLRETHROW;
                }
                return;
            }
        }

        if(pRequestInfo->pfnAsyncComplete != NULL)
            ATLTRY((*pRequestInfo->pfnAsyncComplete)(pRequestInfo, cbIO, dwError));

        if(pRequestInfo->dwRequestState == ATLSRV_STATE_DONE)
        {
            pRequestInfo->pExtension->RequestComplete(pRequestInfo, HTTP_ERROR_CODE(HTTP_SUCCESS), 0);
        }
        else if(pRequestInfo->dwRequestState == ATLSRV_STATE_CACHE_DONE)
        {
            CloseHandle(pRequestInfo->hFile);
            pRequestInfo->pFileCache->ReleaseFile(pRequestInfo->hEntry);
            pRequestInfo->pExtension->RequestComplete(pRequestInfo, HTTP_ERROR_CODE(HTTP_SUCCESS), 0);
        }
        else
        {
            HANDLE hMutex = pRequestInfo->m_hMutex;
            pRequestInfo->pExtension->QueueRequest(pRequestInfo);
            if(hMutex)
                ReleaseMutex(hMutex);
        }
    }

    void HandleError(__in IHttpServerContext *pServerContext, __in DWORD dwStatus, __in DWORD dwSubStatus) throw()
    {
        RenderError(pServerContext, dwStatus, dwSubStatus, &m_UserErrorProvider);
    }

    void RequestComplete(__inout AtlServerRequest *pRequestInfo, __in DWORD dwStatus, __in DWORD dwSubStatus)
    {
        ATLASSERT(pRequestInfo);

        if(pRequestInfo->pHandler != NULL)
            pRequestInfo->pHandler->UninitializeHandler();

        DWORD dwReqStatus = dwStatus;
        if(!dwReqStatus)
            dwReqStatus = 200;

        if(dwStatus >= 400)
        {
            if(dwSubStatus != SUBERR_NO_PROCESS)
                HandleError(pRequestInfo->pServerContext, dwStatus, dwSubStatus);
            m_reqStats.RequestHandled(pRequestInfo, FALSE);
        }
        else
            m_reqStats.RequestHandled(pRequestInfo, TRUE);

        CComPtr<IHttpServerContext> spServerContext = pRequestInfo->pServerContext;

        FreeRequest(pRequestInfo);

        spServerContext->DoneWithSession(dwReqStatus);
    }

    HTTP_CODE GetHandlerName(__in LPCSTR szFileName, __out_ecount(MAX_PATH + ATL_MAX_HANDLER_NAME + 2) LPSTR szHandlerName) throw()
    {
        return _AtlGetHandlerName(szFileName, szHandlerName);
    }

    HTTP_CODE LoadDispatchFile(__in LPCSTR szFileName, __out AtlServerRequest *pRequestInfo)
    {
        ATLASSERT(szFileName);
        ATLASSERT(pRequestInfo);

        CStencil *pStencil = NULL;
        HCACHEITEM hStencil = NULL;

        // Must have space for the path to the handler + the maximum size
        // of the handler, plus the '/' plus the '\0'
        CHAR szDllPath[MAX_PATH];
        CHAR szHandlerName[ATL_MAX_HANDLER_NAME_LEN+1];

        pRequestInfo->pHandler = NULL;
        pRequestInfo->hInstDll = NULL;

        m_StencilCache.LookupStencil(szFileName, &hStencil);

        // Stencil was found, check to see if it needs to be refreshed
        if(hStencil)
        {
            m_StencilCache.GetStencil(hStencil, (void **) &pStencil);
            pStencil->GetHandlerName(szDllPath, MAX_PATH, szHandlerName, ATL_MAX_HANDLER_NAME_LEN + 1);

            CFileTime cftCurr;
            CFileTime cftLastChecked;
            cftCurr = CFileTime::GetCurrentTime();

            pStencil->GetLastChecked(&cftLastChecked);

            CFileTimeSpan span(ATL_STENCIL_CHECK_TIMEOUT * CFileTime::Millisecond);

            if(cftLastChecked + span < cftCurr)
            {
                CComPtr<IStencilCacheControl> spCacheCtrl;
                m_StencilCache.QueryInterface(__uuidof(IStencilCacheControl), reinterpret_cast<void**>(&spCacheCtrl));
                if(spCacheCtrl)
                {
                    CFileTime cftLastModified;
                    pStencil->GetLastModified(&cftLastModified);

                    // Resource based stencils have a last modified filetime of 0
                    if(cftLastModified != 0)
                    {
                        // for file base stencils, we check whether the file
                        // has been modified since being put in the cache
                        WIN32_FILE_ATTRIBUTE_DATA fad;
                        pStencil->SetLastChecked(&cftCurr);
                        BOOL bRet = GetFileAttributesExA(szFileName, GetFileExInfoStandard, &fad);

                        if((bRet && cftLastModified < fad.ftLastWriteTime) ||
                           !bRet)
                        {
                            // the file has changed or an error has occurred trying to read the file,
                            // so remove it from the cache and force a reload
                            spCacheCtrl->RemoveStencil(hStencil);
                            pStencil = NULL;
                            hStencil = NULL;
                        }
                    }
                }
            }
        }


        if(!hStencil)
        {
            CHAR szHandlerDllName[MAX_PATH+ATL_MAX_HANDLER_NAME_LEN+1];
            *szHandlerDllName = '\0';

            // not in the cache, so open the file
            HTTP_CODE hcErr = GetHandlerName(szFileName, szHandlerDllName);
            if(hcErr)
                return hcErr;
            DWORD dwDllPathLen = MAX_PATH;
            DWORD dwHandlerNameLen = ATL_MAX_HANDLER_NAME_LEN + 1;
            if(!_AtlCrackHandler(szHandlerDllName, szDllPath, &dwDllPathLen, szHandlerName, &dwHandlerNameLen))
            {
                return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
            }
            ATLASSERT(*szHandlerName);
            ATLASSERT(*szDllPath);
            if(!*szHandlerName)
            {
                return AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
            }
        }
        else
        {
            m_StencilCache.ReleaseStencil(hStencil);
        }


        return LoadRequestHandler(szDllPath, szHandlerName, pRequestInfo->pServerContext,
                                  &pRequestInfo->hInstDll, &pRequestInfo->pHandler);
    }

    HTTP_CODE LoadDllHandler(__in LPCSTR szFileName, __in AtlServerRequest *pRequestInfo)
    {
        ATLASSERT(szFileName);
        ATLENSURE(pRequestInfo);

        _ATLTRY
        {
            HTTP_CODE hcErr = HTTP_SUCCESS;
            CHAR szHandler[ATL_MAX_HANDLER_NAME_LEN+1] = { 'D', 'e', 'f', 'a', 'u', 'l', 't', '\0' };
            LPCSTR szQueryString = pRequestInfo->pServerContext->GetQueryString();
            if(szQueryString != NULL)
            {
                LPCSTR szHdlr = strstr(szQueryString, "Handler=");
                if(szHdlr != NULL)
                {
                    if((szHdlr == szQueryString) ||
                    ((szHdlr > szQueryString) && (*(szHdlr - 1) == '&')))
                    {
                        int nCnt = 0;
                        LPSTR pszHandler = szHandler;
                        szHdlr += sizeof("Handler=") - 1;
                        while(*szHdlr && *szHdlr != '&')
                        {
                            if(nCnt < ATL_MAX_HANDLER_NAME_LEN)
                            {
                                *pszHandler++ = *szHdlr++;
                                nCnt++;
                            }
                            else
                            {
                                hcErr = AtlsHttpError(500, ISE_SUBERR_HANDLER_NOT_FOUND);
                                break;
                            }
                        }
                        if(hcErr == HTTP_SUCCESS)
                        {
                            *pszHandler = '\0';
                        }
                    }
                }
            }

            if(hcErr == HTTP_SUCCESS)
            {
                CHAR szFile[MAX_PATH+ATL_MAX_HANDLER_NAME_LEN+1];
                if(SafeStringCopy(szFile, szFileName))
                {
                    hcErr = LoadRequestHandler(szFile, szHandler, pRequestInfo->pServerContext, &pRequestInfo->hInstDll, &pRequestInfo->pHandler);
                }
                else
                {
                    hcErr = AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
                }
            }

            return hcErr;
        }
        _ATLCATCHALL()
        {
            return AtlsHttpError(500, ISE_SUBERR_UNEXPECTED);
        }
    }

#pragma warning(push)
#pragma warning(disable: 6014)
    virtual __success(return) __checkReturn BOOL GetCacheServerContext(__in AtlServerRequest *pRequestInfo, __in IFileCache *pCache, __deref_out_opt IHttpServerContext **pCacheCtx)
    {
        ATLENSURE(pCacheCtx);
        *pCacheCtx = NULL;

        CComObjectNoLock<CCacheServerContext> *pCacheServerContext = NULL;
        ATLTRY(pCacheServerContext = new CComObjectNoLock<CCacheServerContext>);
        if(!pCacheServerContext)
            return FALSE;

        if(!pCacheServerContext->Initialize(pRequestInfo->pServerContext, pCache))
        {
            delete pCacheServerContext;
            return FALSE;
        }

        pCacheServerContext->QueryInterface(__uuidof(IHttpServerContext), (void **) pCacheCtx);

        if(*pCacheCtx)
            return TRUE;

        delete pCacheServerContext;
        return FALSE;
    }
#pragma warning(pop)

    virtual BOOL TransmitFromCache(__in AtlServerRequest* pRequestInfo, __out BOOL *pbAllowCaching)
    {
        ATLENSURE(pRequestInfo);
        ATLENSURE(pbAllowCaching);

        *pbAllowCaching = TRUE;

        _ATLTRY
        {
            if(strcmp(pRequestInfo->pServerContext->GetRequestMethod(), "GET"))
                return FALSE;

            char szUrl[ATL_URL_MAX_URL_LENGTH + 1];
            LPCSTR szPathInfo = pRequestInfo->pServerContext->GetPathInfo();
            LPCSTR szQueryString = pRequestInfo->pServerContext->GetQueryString();

            int nSize = 0;
            LPSTR szTo = szUrl;
            ATLENSURE(szPathInfo != NULL);
            while(*szPathInfo && nSize < ATL_URL_MAX_URL_LENGTH)
            {
                *szTo++ = *szPathInfo++;
                nSize++;
            }
            if(nSize >= ATL_URL_MAX_URL_LENGTH)
            {
                return FALSE;
            }
            *szTo++ = '?';
            nSize++;
            ATLENSURE(szQueryString != NULL);
            while(*szQueryString && nSize < ATL_URL_MAX_URL_LENGTH)
            {
                *szTo++ = *szQueryString++;
                nSize++;
            }
            if(nSize >= ATL_URL_MAX_URL_LENGTH)
            {
                return FALSE;
            }
            *szTo = '\0';

            HCACHEITEM hEntry;

            if(S_OK == m_PageCache.LookupFile(szUrl, &hEntry))
            {
                LPSTR szFileName;
                CPageCachePeer::PeerInfo *pInfo;
                m_PageCache.GetFile(hEntry, &szFileName, (void **)&pInfo);
                ATLENSURE(pInfo);
                CAtlFile file;
                HRESULT hr = E_FAIL;

                CA2CTEX<MAX_PATH> strFile(szFileName);
                hr = file.Create(strFile,
                GENERIC_READ,
                FILE_SHARE_READ,
                OPEN_EXISTING,
                FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED);

                if(FAILED(hr) || GetFileType(file) != FILE_TYPE_DISK)
                {
                    m_PageCache.ReleaseFile(hEntry);
                    *pbAllowCaching = FALSE;
                    return FALSE;
                }

                pRequestInfo->pServerContext->SendResponseHeader(
                    pInfo->strHeader, pInfo->strStatus, FALSE);
                HANDLE hFile = file.Detach();
                BOOL bRet = FALSE;

                pRequestInfo->dwRequestState = ATLSRV_STATE_CACHE_DONE;
                pRequestInfo->hFile = hFile;
                pRequestInfo->hEntry = hEntry;
                pRequestInfo->pFileCache = &m_PageCache;

                bRet = pRequestInfo->pServerContext->TransmitFile(
                    hFile,                          // The file to transmit
                    AsyncCallback, pRequestInfo,    // The async callback and context
                    pInfo->strStatus,               // HTTP status code
                    0,                              // Send entire file
                    0,                              // Start at the beginning of the file
                    NULL, 0,                        // Head and length
                    NULL, 0,                        // Tail and length
                    HSE_IO_ASYNC | HSE_IO_DISCONNECT_AFTER_SEND | HSE_IO_NODELAY // Send asynchronously
                );

                if(!bRet)
                {
                    m_PageCache.ReleaseFile(hEntry);
                    CloseHandle(hFile);
                    *pbAllowCaching = FALSE;
                    return FALSE;
                }
                return TRUE;
            }
        }
        _ATLCATCHALL()
        {
        }

        return FALSE;
    }

#if defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)

    BOOL m_bDebug;
    // F5 debugging support for VS7
    BOOL ProcessDebug(__inout AtlServerRequest *pRequestInfo)
    {
        ATLENSURE(pRequestInfo);
        static GUID clsidDebugger[] =
        {
            {0x70F65411, 0xFE8C, 0x4248, {0xBC, 0xFF, 0x70, 0x1C, 0x8B, 0x2F, 0x45, 0x29}}, // debugger clsid
            {0x62A78AC2, 0x7D9A, 0x4377, {0xB9, 0x7E, 0x69, 0x65, 0x91, 0x9F, 0xDD, 0x02}}, // reserved
            {0xCC23651F, 0x4574, 0x438F, {0xB4, 0xAA, 0xBC, 0xB2, 0x8B, 0x6B, 0x3E, 0xCF}}, // reserved
            {0xDBFDB1D0, 0x04A4, 0x4315, {0xB1, 0x5C, 0xF8, 0x74, 0xF6, 0xB6, 0xE9, 0x0B}}, // reserved
            {0xA4FCB474, 0x2687, 0x4924, {0xB0, 0xAD, 0x7C, 0xAF, 0x33, 0x1D, 0xB8, 0x26}}, // reserved
            {0xBEB261F6, 0xD5F0, 0x43BA, {0xBA, 0xF4, 0x8B, 0x79, 0x78, 0x5F, 0xFF, 0xAF}}, // reserved
            {0x8E2F5E28, 0xD4E2, 0x44C0, {0xAA, 0x02, 0xF8, 0xC5, 0xBE, 0xB7, 0x0C, 0xAC}}, // reserved
            {0x08100915, 0x0F41, 0x4CCF, {0x95, 0x64, 0xEB, 0xAA, 0x5D, 0x49, 0x44, 0x6C}} // reserved
        };
        _ATLTRY
        {
            if(!_stricmp(pRequestInfo->pServerContext->GetRequestMethod(), "debug"))
            {
                // Debugger must be able to validate the client we are impersonating
                // on an NT Domain so the client needs to use either NTLM or Negotiate.
                DWORD dwAuthTypeSize = 64;
                char szAuthType[64] = { 0 };

                if(!pRequestInfo->pServerContext->GetServerVariable("AUTH_TYPE", szAuthType, &dwAuthTypeSize))
                {
                    // error retrieving authentication type
                    RequestComplete(pRequestInfo, 501, 0);
                    return FALSE;
                }

                // if it's empty or not NTLM or negotiate we fail.
                if(!(*szAuthType != '\0 ' &&
                (!_stricmp(szAuthType, "NTLM") ||
                !_stricmp(szAuthType, "Negotiate"))
                    ))
                {
                    // wrong authorization type or not authorized
                    RequestComplete(pRequestInfo, 401, 0);
                    return FALSE;
                }

                DWORD dwHeadersLen = 0;
                CStringA strHeaders;
                pRequestInfo->pServerContext->GetServerVariable("ALL_HTTP", NULL, &dwHeadersLen);
                BOOL bRet = pRequestInfo->pServerContext->GetServerVariable("ALL_HTTP", strHeaders.GetBuffer(dwHeadersLen), &dwHeadersLen);
                if(!bRet)
                {
                    RequestComplete(pRequestInfo, 501, 0);
                    return FALSE;
                }
                strHeaders.ReleaseBuffer(dwHeadersLen - 1);
                LPCSTR szCur = strHeaders;

                while(*szCur)
                {
                    if(!strncmp(szCur, "HTTP_COMMAND:", 13))
                    {
                        szCur += 13;
                        break;
                    }

                    szCur = strchr(szCur, '\n');
                    if(!szCur)
                    {
                        RequestComplete(pRequestInfo, 501, 0);
                        return FALSE;
                    }

                    szCur++;
                }


                if(!_strnicmp(szCur, "start-debug", sizeof("start-debug") - sizeof('\0')))
                {
                    CCritSecLock Lock(m_critSec.m_sec);
                    if(m_bDebug)
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_ALREADY_DEBUGGING);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_ALREADY_DEBUGGING);   // Already being debugged by another process
                        return FALSE;
                    }
                    CHttpRequest HttpRequest;
                    HttpRequest.Initialize(pRequestInfo->pServerContext);
                    HttpRequest.InitFromPost();
                    LPCSTR szString;
                    szString = HttpRequest.FormVars.Lookup("DebugSessionID");
                    if(!szString || !*szString)
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_INVALID_SESSION);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_INVALID_SESSION);
                        return FALSE;
                    }
                    CA2W szSessionID(szString);
                    if(!szSessionID)
                    {
                        HandleError(pRequestInfo->pServerContext, 500, ISE_SUBERR_OUTOFMEM);
                        RequestComplete(pRequestInfo, 500, ISE_SUBERR_OUTOFMEM);
                        return FALSE;
                    }
                    DWORD dwPid = GetCurrentProcessId();
                    LPWSTR szPoint = szSessionID;
                    while(szPoint && *szPoint && wcsncmp(szPoint, L"autoattachclsid=", 16))
                    {
                        szPoint = wcschr(szPoint, ';');
                        if(szPoint)
                            szPoint++;
                    }

                    if(!szPoint || !*szPoint)
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
                        return FALSE;
                    }

                    szPoint += (sizeof("autoattachclsid=") - 1);
                    WCHAR szClsid[39];
                    szClsid[38] = '\0';
                    Checked::wcsncpy_s(szClsid, _countof(szClsid), szPoint, _TRUNCATE);
                    if(szClsid[38] != '\0')
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
                        return FALSE;
                    }
                    szClsid[38] = '\0';

                    CLSID clsidDebugAutoAttach = CLSID_NULL;
                    HRESULT hr = CLSIDFromString(szClsid, &clsidDebugAutoAttach);

                    if(hr != S_OK)
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
                        return FALSE;
                    }

                    size_t i = 0,
                    nArrSize = sizeof(clsidDebugger) / sizeof(GUID);
                    for(i = 0; i < nArrSize; i++)
                    {
                        if(InlineIsEqualGUID(clsidDebugAutoAttach, clsidDebugger[i]))
                            break;
                    }
                    if(i >= nArrSize)
                    {
                        HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_BAD_ID);
                        RequestComplete(pRequestInfo, 204, DBG_SUBERR_BAD_ID);
                        return FALSE;
                    }

                    CComPtr<IDebugAutoAttach> spDebugAutoAttach;
                    hr = CoCreateInstance(clsidDebugAutoAttach, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, __uuidof(IDebugAutoAttach), (void**)&spDebugAutoAttach);
                    if(FAILED(hr))
                    {
                        if(hr == E_ACCESSDENIED)
                            RequestComplete(pRequestInfo, 401, 0);
                        else
                        {
                            HandleError(pRequestInfo->pServerContext, 204, DBG_SUBERR_COCREATE);
                            RequestComplete(pRequestInfo, 204, DBG_SUBERR_COCREATE);
                        }
                        return FALSE;
                    }
                    hr = spDebugAutoAttach->AutoAttach(GUID_NULL, dwPid, AUTOATTACH_PROGRAM_WIN32, 0, szSessionID);
                    if(FAILED(hr))
                    {
                        char szRetBuf[256];
                        int nLen = sprintf_s(szRetBuf, _countof(szRetBuf), "204 HRESULT=0x%.08X;ErrorString=Unable to attach to worker process", hr);
                        if(nLen > 0)
                        {
                            DWORD dwLen = nLen;
                            pRequestInfo->pServerContext->SendResponseHeader(NULL, szRetBuf, FALSE);
                            pRequestInfo->pServerContext->WriteClient(szRetBuf, &dwLen);
                            RequestComplete(pRequestInfo, 204, DBG_SUBERR_ATTACH);
                        }
                        return FALSE;
                    }
                    m_bDebug = TRUE;
                    HandleError(pRequestInfo->pServerContext, 200, SUBERR_NONE);
                    RequestComplete(pRequestInfo, 200, SUBERR_NONE);
                    return FALSE;
                }
                else if(!_strnicmp(szCur, "stop-debug", sizeof("stop-debug") - sizeof('\0')))
                {
                    m_bDebug = FALSE;
                    HandleError(pRequestInfo->pServerContext, 200, SUBERR_NONE);
                    RequestComplete(pRequestInfo, 200, SUBERR_NONE);
                    return FALSE;
                }
                else
                {
                    RequestComplete(pRequestInfo, 501, SUBERR_NONE);   // Not Implemented
                    return FALSE;
                }
            }
            return TRUE;
        }
        _ATLCATCHALL()
        {
            return FALSE;
        }
    }
#endif // defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)

    BOOL DispatchStencilCall(__inout AtlServerRequest *pRequestInfo)
    {
        ATLENSURE(pRequestInfo != NULL);
        CSetThreadToken sec;

        m_reqStats.OnRequestDequeued();

        if(!sec.Initialize(pRequestInfo))
        {
            RequestComplete(pRequestInfo, 500, ISE_SUBERR_IMPERSONATIONFAILED);
            return FALSE;
        }

#if defined(_DEBUG) || defined(ATLS_ENABLE_DEBUGGING)
        if(!ProcessDebug(pRequestInfo))
            return TRUE;
#endif // defined(ATLS_ENABLE_DEBUGGING)

        if(pRequestInfo->m_hMutex)
        {
            // synchronize in case the previous async_noflush call isn't finished
            // setting up state for the next call.
            DWORD dwStatus = WaitForSingleObject(pRequestInfo->m_hMutex, ATLS_ASYNC_MUTEX_TIMEOUT);
            if(dwStatus != WAIT_OBJECT_0 && dwStatus != WAIT_ABANDONED)
            {
                RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
                return FALSE;
            }
        }

#ifdef _DEBUG
        bool bAsyncAllowed = false;
#endif
        HTTP_CODE hcErr = HTTP_SUCCESS;
        if(pRequestInfo->dwRequestState == ATLSRV_STATE_BEGIN)
        {
            BOOL bAllowCaching = TRUE;
            if(TransmitFromCache(pRequestInfo, &bAllowCaching))     // Page is in the cache, send it and bail
            {
                // Async Callback will handle freeing pRequestInfo
                return TRUE;
            }

            // get the srf filename
            LPCSTR szFileName = pRequestInfo->pServerContext->GetScriptPathTranslated();

            if(!szFileName)
            {
                RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
                return FALSE;
            }

            LPCSTR szDot = szFileName + strlen(szFileName) - 1;

            // load a handler
            if(AsciiStricmp(szDot - ATLS_EXTENSION_LEN, c_AtlSRFExtension) == 0)
            {
                pRequestInfo->dwRequestType = ATLSRV_REQUEST_STENCIL;
                hcErr = LoadDispatchFile(szFileName, pRequestInfo);
            }
            else if(AsciiStricmp(szDot - ATLS_DLL_EXTENSION_LEN, c_AtlDLLExtension) == 0)
            {
                pRequestInfo->dwRequestType = ATLSRV_REQUEST_DLL;
                hcErr = LoadDllHandler(szFileName, pRequestInfo);
            }
            else
            {
                hcErr = HTTP_FAIL;
            }

            if(hcErr)
            {
                RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
                return TRUE;
            }

            pRequestInfo->pfnHandleRequest = &IRequestHandler::HandleRequest;

            // initialize the handler
            DWORD dwStatus = 0;

            hcErr = pRequestInfo->pHandler->GetFlags(&dwStatus);
            if(hcErr)
            {
                RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
                return FALSE;
            }

            if(bAllowCaching && ((dwStatus & ATLSRV_INIT_USECACHE) != 0) &&
               !strcmp(pRequestInfo->pServerContext->GetRequestMethod(), "GET"))
            {
                CComPtr<IHttpServerContext> spCacheCtx;
                if(!GetCacheServerContext(pRequestInfo, &m_PageCache, &spCacheCtx) ||
                   !spCacheCtx)
                {
                    RequestComplete(pRequestInfo, 500, ISE_SUBERR_OUTOFMEM);
                    return FALSE;
                }

                pRequestInfo->pServerContext->Release();
                pRequestInfo->pServerContext = spCacheCtx.Detach();
            }

            if(dwStatus & (ATLSRV_INIT_USEASYNC | ATLSRV_INIT_USEASYNC_EX))
            {
#ifdef _DEBUG
                bAsyncAllowed = true;
#endif
                if(!pRequestInfo->pServerContext->RequestIOCompletion(AsyncCallback, (DWORD *)pRequestInfo))
                {
                    RequestComplete(pRequestInfo, 500, SUBERR_NONE);
                    return FALSE;
                }
            }

            if(dwStatus & ATLSRV_INIT_USEASYNC_EX)
            {
                pRequestInfo->m_hMutex = CreateMutex(NULL, FALSE, NULL);
                if(pRequestInfo->m_hMutex == NULL)
                {
                    RequestComplete(pRequestInfo, 500, ISE_SUBERR_SYSOBJFAIL);
                    return FALSE;
                }

                DWORD dwMutexStatus = WaitForSingleObject(pRequestInfo->m_hMutex, 10000);
                if(dwMutexStatus != WAIT_OBJECT_0 && dwMutexStatus != WAIT_ABANDONED)
                {
                    RequestComplete(pRequestInfo, 500, ISE_SUBERR_UNEXPECTED);
                    return FALSE;
                }
            }
            hcErr = pRequestInfo->pHandler->InitializeHandler(pRequestInfo, static_cast<IServiceProvider*>(this));
        }
#ifdef _DEBUG
        else // pRequestInfo->dwRequestState != ATLSRV_STATE_BEGIN
        {
            bAsyncAllowed = true;
        }
#endif

        ATLENSURE(pRequestInfo->pfnHandleRequest != NULL);

        if(hcErr == HTTP_SUCCESS)
            hcErr = (pRequestInfo->pHandler->*pRequestInfo->pfnHandleRequest)(pRequestInfo, static_cast<IServiceProvider*>(this));

        if(hcErr == HTTP_SUCCESS_NO_CACHE)
        {
            CComPtr<IPageCacheControl> spControl;
            HRESULT hr = pRequestInfo->pServerContext->QueryInterface(__uuidof(IPageCacheControl), reinterpret_cast<void**>(&spControl));
            if(hr == S_OK)
                spControl->Cache(FALSE);
        }

#ifdef _DEBUG
        // must use ATLSRV_INIT_USEASYNC to use ASYNC returns
        if(IsAsyncStatus(hcErr))
            ATLASSERT(bAsyncAllowed);

        // must use ATLSRV_INIT_USEASYNC_EX to use NOFLUSH returns
        if(IsAsyncNoFlushStatus(hcErr))
            ATLASSERT(pRequestInfo->m_hMutex);
#endif

        // save hMutex in case pRequestInfo is deleted by AsyncCallback after
        // we call StartAsyncFlush but before we check to see if we need to
        // call ReleaseMutex
        HANDLE hMutex = pRequestInfo->m_hMutex;

        if(IsAsyncStatus(hcErr))
        {
            if(IsAsyncDoneStatus(hcErr))
                pRequestInfo->dwRequestState = ATLSRV_STATE_DONE;
            else
                pRequestInfo->dwRequestState = ATLSRV_STATE_CONTINUE;

            if(IsAsyncFlushStatus(hcErr) && !StartAsyncFlush(pRequestInfo))
            {
                RequestComplete(pRequestInfo, 500, SUBERR_NONE);
                pRequestInfo = NULL;
            }
        }
        else
        {
            RequestComplete(pRequestInfo, HTTP_ERROR_CODE(hcErr), HTTP_SUBERROR_CODE(hcErr));
            pRequestInfo = NULL;
        }

        if(hMutex)
            ReleaseMutex(hMutex);

        return TRUE;
    }

    BOOL StartAsyncFlush(__in AtlServerRequest *pRequestInfo)
    {
        ATLENSURE(pRequestInfo);
        if(pRequestInfo->pszBuffer == NULL || pRequestInfo->dwBufferLen == 0)
        {
            ATLASSERT(FALSE);
            return FALSE;
        }

        ATLENSURE(pRequestInfo->pServerContext);
        BOOL bRet = TRUE;
        _ATLTRY
        {
            bRet = pRequestInfo->pServerContext->AsyncWriteClient(
                LPVOID(pRequestInfo->pszBuffer),
                &pRequestInfo->dwBufferLen);
        }
        _ATLCATCHALL()
        {
            bRet = FALSE;
        }

        return bRet;
    }

    long GetTotalRequests()
    {
        return m_reqStats.GetTotalRequests();
    }

    long GetFailedRequests()
    {
        return m_reqStats.GetFailedRequests();
    }

    long GetAvgResponseTime()
    {
        return m_reqStats.GetAvgResponseTime();
    }

    long GetCurrWaiting()
    {
        return m_reqStats.GetCurrWaiting();
    }

    long GetMaxWaiting()
    {
        return m_reqStats.GetMaxWaiting();
    }

    long GetActiveThreads()
    {
        return m_reqStats.GetActiveThreads();
    }

    __success(SUCCEEDED(return)) __checkReturn HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __deref_out void **ppv)
    {
        if(!ppv)
            return E_POINTER;
        if(InlineIsEqualGUID(riid, __uuidof(IRequestStats)))
        {
            *ppv = static_cast<IRequestStats*>(this);
            AddRef();
            return S_OK;
        }
        if(InlineIsEqualGUID(riid, __uuidof(IUnknown)) ||
           InlineIsEqualGUID(riid, __uuidof(IServiceProvider)))
        {
            *ppv = static_cast<IServiceProvider*>(this);
            AddRef();
            return S_OK;
        }
        if(InlineIsEqualGUID(riid, __uuidof(IIsapiExtension)))
        {
            *ppv = static_cast<IIsapiExtension*>(this);
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return 1;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        return 1;
    }

    virtual __checkReturn HRESULT STDMETHODCALLTYPE QueryService(
        __in REFGUID guidService,
        __in REFIID riid,
        __deref_out void **ppvObject)
    {
        if(!ppvObject)
            return E_POINTER;

        if(InlineIsEqualGUID(guidService, __uuidof(IDllCache)))
            return m_DllCache.QueryInterface(riid, ppvObject);
        else if(InlineIsEqualGUID(guidService, __uuidof(IStencilCache)))
            return m_StencilCache.QueryInterface(riid, ppvObject);
        else if(InlineIsEqualGUID(guidService, __uuidof(IThreadPoolConfig)))
            return m_ThreadPool.QueryInterface(riid, ppvObject);
        else if(InlineIsEqualGUID(guidService, __uuidof(IAtlMemMgr)))
        {
            *ppvObject = static_cast<IAtlMemMgr *>(&m_heap);
            return S_OK;
        }
#ifndef ATL_NO_SOAP
        else if(InlineIsEqualGUID(guidService, __uuidof(ISAXXMLReader)))
        {
            CIsapiWorker *p = GetThreadWorker();
            ATLENSURE(p != NULL);
            return p->m_spReader->QueryInterface(riid, ppvObject);
        }
#endif

        // otherwise look it up in the servicemap
        return GetService(guidService, riid, ppvObject);
    }

    virtual HRESULT AddService(REFGUID guidService, REFIID riid, IUnknown *punkService, HINSTANCE hInstance)
    {
        if(!punkService)
            return E_INVALIDARG;

        if(!m_DllCache.AddRefModule(hInstance))
            return E_FAIL;

        ServiceNode srvNode;
        srvNode.hInst = hInstance;
        srvNode.punk = punkService;
        Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
        Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

        CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
        HRESULT hr = lock.Lock();
        if(FAILED(hr))
        {
            return hr;
        }

        // if the service is already there, return S_FALSE
        int nIndex = m_serviceMap.Find(srvNode);
        if(nIndex >= 0)
            return S_FALSE;

        if(!m_serviceMap.Add(srvNode))
            return E_OUTOFMEMORY;

        punkService->AddRef();
        return S_OK;
    }

    virtual HRESULT RemoveService(__in REFGUID guidService, __in REFIID riid)
    {
        ServiceNode srvNode;
        Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
        Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

        CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
        HRESULT hr = lock.Lock();
        if(FAILED(hr))
        {
            return hr;
        }

        int nIndex = m_serviceMap.Find(srvNode);
        if(nIndex < 0)
            return S_FALSE;

        ATLASSUME(m_serviceMap[nIndex].punk != NULL);
        m_serviceMap[nIndex].punk->Release();

        HINSTANCE hInstRemove = m_serviceMap[nIndex].hInst;

        m_serviceMap.RemoveAt(nIndex);

        if(!m_DllCache.ReleaseModule(hInstRemove))
            return S_FALSE;

        return S_OK;
    }

    __success(SUCCEEDED(return)) __checkReturn HRESULT GetService(__in REFGUID guidService, __in REFIID riid, __deref_out void **ppvObject) throw()
    {
        if(!ppvObject)
            return E_POINTER;

        *ppvObject = NULL;
        if(!m_serviceMap.GetSize())
            return E_NOINTERFACE;

        ServiceNode srvNode;
        Checked::memcpy_s(&srvNode.guidService, sizeof(GUID), &guidService, sizeof(guidService));
        Checked::memcpy_s(&srvNode.riid, sizeof(IID), &riid, sizeof(riid));

        CComCritSecLock<CComCriticalSection> lock(m_critSec, false);
        HRESULT hr = lock.Lock();
        if(FAILED(hr))
        {
            return hr;
        }

        int nIndex = m_serviceMap.Find(srvNode);
        if(nIndex < 0)
            return E_NOINTERFACE;

        ATLASSUME(m_serviceMap[nIndex].punk != NULL);
        return m_serviceMap[nIndex].punk->QueryInterface(riid, ppvObject);
    }

    HTTP_CODE LoadRequestHandler(__in LPCSTR szDllPath, __in LPCSTR szHandlerName, __in IHttpServerContext *pServerContext,
                                 __out HINSTANCE *phInstance, __deref_out IRequestHandler **ppHandler)
    {
        return _AtlLoadRequestHandler(szDllPath, szHandlerName, pServerContext,
                                      phInstance, ppHandler, this, static_cast<IDllCache*>(&m_DllCache));
    } // LoadRequestHandler

}; // class CIsapiExtension


//===========================================================================================
// IMPORTANT NOTE TO USERS:
// DO NOT ASSUME *ANYTHING* ABOUT THE STRUCTURE OF THESE MAPS/ENTRIES/FUNCTIONS--THEY CAN
// AND *WILL* CHANGE IN THE FUTURE. CORRECT USAGE MANDATES THAT YOU USE THE MACROS PROVIDED.
// ABSOLUTELY NO GUARANTEES ABOUT BACKWARD COMPATABILITY ARE MADE FOR MANUALLY DEFINED
// HANDLERS OR FUNCTIONS.
//===========================================================================================

typedef BOOL (*CREATEHANDLERFUNC)(IIsapiExtension *pExtension, IUnknown **ppOut);
typedef BOOL (*INITHANDLERFUNC)(IHttpServerContext*, IIsapiExtension*);
typedef void (*UNINITHANDLERFUNC)();

struct _HANDLER_ENTRY
{
    LPCSTR szName;
    CREATEHANDLERFUNC pfnCreate;
    INITHANDLERFUNC pfnInit;
    UNINITHANDLERFUNC pfnUninit;
};
// definitions of data segments and _HANDLER_ENTRY delimiters
#pragma section("ATLS$A", read, shared)
#pragma section("ATLS$Z", read, shared)
#pragma section("ATLS$C", read, shared)
extern "C"
{
    __declspec(selectany) __declspec(allocate("ATLS$A")) ATL::_HANDLER_ENTRY * __phdlrA = NULL;
    __declspec(selectany) __declspec(allocate("ATLS$Z")) ATL::_HANDLER_ENTRY * __phdlrZ = NULL;
}

#if !defined(_M_IA64)
#pragma comment(linker, "/merge:ATLS=.rdata")
#endif

#ifndef HANDLER_ENTRY_PRAGMA

#if defined(_M_IX86)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:___phdlrEntry_" #class "_" #line));
#elif defined(_M_IA64)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:__phdlrEntry_" #class "_" #line));
#elif defined(_M_AMD64)
#define HANDLER_ENTRY_PRAGMA(class, line) __pragma(comment(linker, "/include:__phdlrEntry_" #class "_" #line));
#else
#error Unknown Platform. define HANDLER_ENTRY_PRAGMA
#endif

#endif // HANDLER_ENTRY_PRAGMA

// DECLARE_REQUEST_HANDLER macro
#define __DECLARE_REQUEST_HANDLER_INTERNAL(handlerName, className, classQName, lineNum) \
__declspec(selectany) ATL::_HANDLER_ENTRY __hdlrEntry_ ## className ## _ ## lineNum = { handlerName, classQName::CreateRequestHandler, classQName::InitRequestHandlerClass, classQName::UninitRequestHandlerClass }; \
extern "C" __declspec(allocate("ATLS$C")) __declspec(selectany) \
ATL::_HANDLER_ENTRY * const __phdlrEntry_ ## className ## _ ## lineNum = &__hdlrEntry_ ## className ## _ ## lineNum; \
HANDLER_ENTRY_PRAGMA(className, lineNum) \
__if_not_exists(GetAtlHandlerByName) \
{ \
extern "C" ATL_NOINLINE inline BOOL __declspec(dllexport) __stdcall GetAtlHandlerByName(LPCSTR szHandlerName, IIsapiExtension *pExtension, IUnknown **ppHandler) throw() \
{ \
	*ppHandler = NULL; \
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName) \
		{ \
			if (strcmp((*pEntry)->szName, szHandlerName)==0) \
			{ \
				return (*(*pEntry)->pfnCreate)(pExtension, ppHandler); \
			} \
		} \
		pEntry++; \
	} \
	return FALSE; \
} \
extern "C" ATL_NOINLINE inline  BOOL __declspec(dllexport) __stdcall InitializeAtlHandlers(IHttpServerContext *pContext, IIsapiExtension *pExt) throw() \
{ \
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	BOOL bRet = TRUE; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName && (*pEntry)->pfnInit) \
		{ \
			bRet = (*(*pEntry)->pfnInit)(pContext, pExt); \
			if (!bRet) \
				break; \
		} \
		pEntry++; \
	} \
	if (!bRet) \
	{ \
		if (pEntry == &__phdlrA) \
			return FALSE; \
		do \
		{ \
			pEntry--; \
			if(*pEntry) \
				(*(*pEntry)->pfnUninit)(); \
		} \
		while (pEntry != &__phdlrA); \
	} \
	return bRet; \
} \
extern "C" ATL_NOINLINE inline void __declspec(dllexport) __stdcall UninitializeAtlHandlers() throw() \
{\
	ATL::_HANDLER_ENTRY **pEntry = &__phdlrA; \
	while (pEntry != &__phdlrZ) \
	{ \
		if (*pEntry && (*pEntry)->szName && (*pEntry)->pfnUninit) \
		{ \
			(*(*pEntry)->pfnUninit)(); \
		} \
		pEntry++; \
	} \
} \
}

#define __DECLARE_REQUEST_HANDLER(handlerName, className, classQName, lineNum) __DECLARE_REQUEST_HANDLER_INTERNAL(handlerName, className, classQName, lineNum)
#define DECLARE_REQUEST_HANDLER(handlerName, className, classQName) __DECLARE_REQUEST_HANDLER(handlerName, className, classQName, __COUNTER__)

#define BEGIN_HANDLER_MAP()
#define HANDLER_ENTRY(handlerName, className) DECLARE_REQUEST_HANDLER(handlerName, className, className)
#define HANDLER_ENTRY_EX(handlerName, className, classQName) DECLARE_REQUEST_HANDLER(handlerName, className, classQName)
#define END_HANDLER_MAP()

#define __HANDLER_ENTRY_SDL_INTERNAL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum)\
_ATLSOAP_DECLARE_WSDL_SRF() \
extern __declspec(selectany) const char const s_szClassName##sdlClassName##lineNum[]=handlerString;\
typedef ATL::CSDLGenerator<handlerQClass, s_szClassName##sdlClassName##lineNum> sdlClassName; \
HANDLER_ENTRY_EX(handlerString, handlerClass, handlerQClass)\
HANDLER_ENTRY(#sdlClassName, sdlClassName)

#define __HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum) __HANDLER_ENTRY_SDL_INTERNAL(handlerString, handlerClass, handlerQClass, sdlClassName, lineNum)
#define HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName) __HANDLER_ENTRY_SDL(handlerString, handlerClass, handlerQClass, sdlClassName, __COUNTER__)
//
// Use this class to check the authorization level of a client who is making
// a request to this application. This class checks for the stronger authentication
// levels (NTLM and Negotiate). You can call it directly from an implementation
// of HandleRequest to check authorization before handling a request.
#define MAX_AUTH_TYPE 50
#define MAX_NAME_LEN 255

template <class T>
class CVerifyAuth
{
public:
    HTTP_CODE IsAuthorized(__in AtlServerRequest *pInfo, __in_opt const SID* psidAuthGroup)
    {
        ATLENSURE(pInfo);
        ATLENSURE(pInfo->pServerContext);
        ATLASSERT(psidAuthGroup);
        ATLASSERT(::IsValidSid((PSID) psidAuthGroup));

        HTTP_CODE hcErr = HTTP_UNAUTHORIZED;
        char szAuthType[MAX_AUTH_TYPE];
        DWORD dwSize = MAX_AUTH_TYPE;
        _ATLTRY
        {
            if(pInfo->pServerContext->GetServerVariable("AUTH_TYPE",
            szAuthType, &dwSize))
            {
                if(szAuthType[0] && (!_stricmp(szAuthType, "NTLM")
                || !_stricmp(szAuthType, "Negotiate")))
                {
                    // if we were passed a group name
                    // we check to see that the logged on user is part
                    // of that group, else we just return success.
                    if(psidAuthGroup)
                    {
                        T* pT = static_cast<T*>(this);
                        if(pT->CheckAccount(pInfo->pServerContext, psidAuthGroup))
                            hcErr = HTTP_SUCCESS;
                        else
                            hcErr = pT->HandleError(pInfo);
                    }
                    else
                        hcErr = HTTP_SUCCESS;
                }
            }
        }
        _ATLCATCHALL()
        {
            hcErr = HTTP_FAIL;
        }

        return hcErr;
    }

    virtual bool CheckAccount(IHttpServerContext *pContext, const SID *psidAuthGroup) throw()
    {
        (pContext); // unused
        (psidAuthGroup); // unused
        return false;
    }

    HTTP_CODE HandleError(AtlServerRequest *pRequestInfo) throw()
    {
        pRequestInfo; // unused
        return HTTP_FAIL;
    }

    __checkReturn bool CheckAuthAccount(__in IHttpServerContext *pContext, __in const SID* psidAuthGroup) throw()
    {
        ATLASSERT(pContext);
        ATLASSERT(psidAuthGroup);
        if(!pContext || !psidAuthGroup)
            return false;

        HANDLE hToken = INVALID_HANDLE_VALUE;
        bool bIsMember;
        _ATLTRY
        {
            if(!pContext->GetImpersonationToken(&hToken) ||
            hToken == INVALID_HANDLE_VALUE)
                return false;

            CAccessToken tok;
            tok.Attach(hToken);
            bool bRet = tok.CheckTokenMembership(CSid(psidAuthGroup), &bIsMember);
            tok.Detach();

            if(!bRet)
                return false;
        }
        _ATLCATCHALL()
        {
            bIsMember = false;
        }

        return bIsMember;
    }
};

// Checks that the user that is logging on is in the required group
class CDefaultAuth :
    public CVerifyAuth<CDefaultAuth>
{
public:
    virtual bool CheckAccount(__in IHttpServerContext *pContext, __in const SID* psidAuthGroup) throw()
    {
        return CheckAuthAccount(pContext, psidAuthGroup);
    }

    HTTP_CODE HandleError(__in AtlServerRequest *pRequestInfo) throw()
    {
        ATLASSERT(pRequestInfo); // should always be valid
        if(!pRequestInfo)
        {
            return HTTP_FAIL;
        }

        _ATLTRY
        {
            CHttpResponse response(pRequestInfo->pServerContext);
            response.Write(GetErrorResponse());
            response.Flush();
        }
        _ATLCATCHALL()
        {
            return HTTP_FAIL;
        }

        return HTTP_SUCCESS_NO_PROCESS;
    }

    virtual LPCSTR GetErrorResponse()
    {
        static const char *szResponse = "<html><body>"
                                        "<H1 align=center>NOT AUTHORIZED</H1><p>"
                                        "</body></html>";
        return szResponse;
    }

};

} // namespace ATL
#pragma pack(pop)

#pragma warning(pop)

#endif // __ATLISAPI_H__
